WWDC 2016 - 419 Protocol and Value Oriented Programming in UIKit Apps
Key words
- An associated type is like a type placeholder. The conforming type chooses the concrete type that it wants to use.
- So associated types are a great way that you make your protocols even more powerful.
- So the first is customization through composition instead of inheritance.
- The second technique is to use protocols for generic reusable code.
Lucid Dreams
Model:
1.struct + enumView:
1.protocol (Drawable & Layout)Controller:
1.State enum + Model struct —> maintainable & single code path
2.withValues(...)
—> state or model coalesces all UI updates
WWDC 2015 - 408 Protocol-Oriented Programming in Swift
OOP
1. Implicit Sharing
- Defensive copy
- Inefficiency
- Race Conditions
- Locks
- More Inefficiency
- DeadLock
- Complexity
You handle something on a dispatch queue and suddenly you've got a race condition because threads are sharing a mutable state, so you start adding locks to protect your invariants.
But the locks slow the code down some more and might even lead to deadlock.
And this is all due to implicit sharing of mutable state, which is inherent to classes.
2. Inheritance Intrusive
- One superclass
- Single Inheritance weight gain
- No retroactive modeling
- Superclass may have stored properties
- You must accept them
- Initialization burden
- Don’t break superclass invariants
- Know what / how to override (and when not to)
classes don't let us express this crucial type relationship between the type of self and the type of other.
A Better Abstraction Mechanism
Supports value types (and classes)
Supports static type relationships (and dynamic dispatch)
Non-monolithic
Supports retroactive modeling
Doesn’t impose instance data on models
Doesn’t impose initialization burdens on models
Makes clear what to implement
protocol Ordered {
func precedes(other: Self) -> Bool
}
struct Number : Ordered {
var value: Double = 0
func precedes(other: Number) -> Bool {
return self.value < other.value
}
}
func binarySearch(sortedKeys: [T], forKey k: T) -> Int {}
Self-requirement. Self in a protocol, it's a placeholder for the type that's going to conform to that protocol, the model type.
Once you add a Self-requirement to a protocol, it moves the protocol into a very different world, where the capabilities have a lot less overlap with classes. It stops being usable as a type.
Protocol 'Ordered' can only be used as a generic constraint
because it has Self or associated type requirements
func precedes(other: Ordered) -> Bool | func precedes(other: Self) -> Bool |
---|---|
Usable as a type | Only usable as a generic constraint |
Func sort(inout a: [Ordered]) | func sort |
extension Ordered where Self : Comparable {
func precedes(other: Self) -> Bool { return self < other }
}
extension Int : Ordered {}
extension String : Ordered {}
binarySearch(sortedKeys: [1, 2, 3], forKey k: 1)
protocol Drawable {
func isEqualTo(other: Drawable) -> Bool
func draw()
}
extension Drawable where Self : equatable {
func isEqualTo(other: Drawable) -> Bool {
if let o = other as? Self { return self == o }
return false
}
}
If you really want to go and handle the heterogeneous case. Go and implement isEqualTo.
When to Use Classes
You want implicit sharing when
Copying or comparing instances doesn’t make sense (Window)
instance lifetime is tied to external effects (e.g. TemporaryFile)
instances are just “sinks” — write-only conduits to external state (e.g. CGContext)
WWDC 2015 - 414 Building Better Apps with Value Types in Swift
Uniquely Referenced Swift Objects
we can use this fact that we have this uniquely referenced property and we know for a fact that something is uniquely referenced so we can avoid making the copies if we know that that reference type is uniquely referenced.
The standard library uses that feature throughout and does a lot of great performance optimizations using that.
struct MyWrapper {
var _object: SomeSwiftObject
var objectForWriting: SomeSwiftObject {
mutating get {
if !isUniquelyReferencedNonObjC(&_object) {
_object = _object.copy()
}
return _object
}
}
}