PS: If you liked this talk or like this concept, let's chat about iOS development at Stitch Fix! #shamelessplug
Speaker: David Abrahams. (Tech lead for Swift standard library)
-
"Crusty" is an old-school programmer who doesn't trust IDE's, debuggers, programming fads. He's cynical, grumpy.
-
OOP has been around since the 1970's. It's not actually new.
-
Classes are Awesome
- Encapsulation
- Access control
- Abstraction
- Namespace
- Expressive syntax
- Extensibility
-
Actually, types are awesome. Three features (access control, abstraction, namespaces) allow us to manage complexity. You can do all that with structs and enums.
-
Crusty's 3 complaints about classes:
- Automatic sharing. Two classes can have a reference to the same data, which causes bugs. You try to copy things to stop these bugs, which slows the app down, which causes race conditions, so you add locks, which slows things down more, and then you have deadlock and more complexity. BUGS! This is all due to implicit sharing of mutable state. Swift collections are all value types, so these issues don't happen
- Inheritance is too intrusive. You can only have 1 super class. You end up bloating your super class. Super classes might have stored properties, which bloat it. Initialization is a pain. You don't want to break super class invariants. You have to know what to over ride, and how. This is why we use delegation in Cocoa
- Lost type relationships. You can't count on subclasses to implement some method, e.g:
class Ordered {
func precedes(other: Ordered) -> Bool { fatalError("implement me") }
}
class Number: Ordered{
var value: Int
override func precedes(other: Ordered) -> Bool {
return value < other.value //THIS IS THE PROBLEM! WHAT TYPE IS ORDRED?! Does it have a value? Let's force type cast it
}
}
- Swift is a Protocol-Oriented language
V1:
protocol Ordered {
func precedes(other: Ordered) -> Bool // No implementation, no runtime error!
}
struct Number: Ordered {
var value: Double = 0
func precedes(other: Ordered) -> Bool {
return self.value < (other as! Number).value
}
}
V2:
protocol Ordered {
//Self is a placeholder for the type that will conform to that protocol. So cool!
func precedes(other: Self) -> Bool
}
struct Number: Ordered {
var value: Double = 0
func precedes(other: Ordered) -> Bool {
return self.value < (other as! Number).value
}
}
func binarySearch<T: Ordered>(sortedKeys:[T], forKey k: T) -> Int {...}
- Basically, use
Self
in protocols. Guarentees more build-time safety.
protocol Drawable {
func draw(renderer: Renderer)
}
protocol Renderer {
func moveTo(p: CGPoint)
func lineTo(p: CGPoint)
func arcAt(center....)
}
struct Circle: Drawable { ... }
struct Polygon: Drawable { ... }
struct Diagram: Drawable {
var elements: [Drawable]
func draw(render: Renderer) {
for e in self.elements {
e.draw(renderer)
}
}
}
//For test purposes
struct TestRenderer: Renderer {
//Implementations all print the points
}
//For drawing purposes. You can extend a class to conform to a protocol
extension CGContext: Renderer {
// Implementations actually perform drawing
}
- Way better than mocks, which don't play well with Swift's type system
- Lets you extend a protocol with a default implementation of a method. Why would you do this?
- Requirements (ie required method in a protocol) creeate customization points
- Sometimes you want custom behavior, sometimes you don't.
- You can constrain an extension (eg:
extension Ordred where Self: Comparable { ... }
) - Generic beautification:
extension CollectionType where Index == RandomAccessIndexType, Generator.Element: Ordered {
//Implementation of method doesn't need angled brackets
}
- Always make value types
Equatable
. Why? Go to value types talk. (Probably because it increases testability).- Can't implement
==
forDrawable
type. (I missed why?) - Require
Drawable
structs to implementfunc isEqualTo(other: Drawable) -> Bool
- Then, `extension Drawable where Self: Equtable { // check whether (missed this code) }
- Can't implement
- Copying or comparing instances doesn't make sense, i.e Window
- Lifetime of the instance is tied to an external side effect, i.e files appearing on disk. Why? Values are created liberally by compiler.
- Instances are just sinks w/ write-only access to external state -- i.e our
Renderer
implementations could have been a class. - Framework expects you to subclass or pass an object.
- Consider making classes
final
, so they can't be subclasses. If you want to subclass it - consider abstracting behavior into a protocol
protocol someProtocol {
var p1:string
}
extension someProtocol {
func configP1(value:string){
self.p1 = value //error
}
if the Self can use the protocol's properties ,it will be perfect