<html><head><style>body{font-family:Helvetica,Arial;font-size:13px}</style></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;"><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">The one-to-many observer pattern could really use a first-party, native Swift solution. The day-to-day practice of writing iOS / OS X applications needs it, and we end up falling back on antiquated APIs like KVO or NSNotificationCenter, or short-lived third-party libraries. This is an essential need that deserves a fresh approach.</div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">What follows is a rough proposal for a Swift-native “KVO alternative”. </div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><b>What Usage Would Look Like:</b></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="color: rgb(0, 0, 0); margin: 0px;"><pre style="margin-top: 0px; margin-bottom: 1.65em; margin-left: -30px; word-wrap: normal; line-height: 1.41em; border: 1px solid rgb(221, 221, 221); background-color: rgb(240, 240, 240); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; box-sizing: border-box; padding: 14px; width: 600px; overflow: auto; color: rgb(64, 64, 64);"><font face="Courier"><span style="font-size: 12px;">let tweet = Tweet(text: “Hi.”)
tweet.observables.isLiked.addObserver { (oldValue, newValue) -> Void in
// this is otherwise just a standard closure, with identical
// memory management rules and variable scope semantics.
print(“Value changed.”)
}
tweet.isLiked = true // Console would log “Value changed.”</span></font></pre></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><b>Behind the Scenes:</b></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">- When compiling a Swift class “Foo", the compiler would also generate a companion “Foo_SwiftObservables” class.</div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">- When initializing an instance of a Swift class, an instance of the companion “ Foo_SwiftObservables” class would be initialized and set as the value of a reserved member name like “observables”. This member would be implicit, like `self`.</div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">- The auto-generated “ Foo_SwiftObservables” class would have a corresponding property for every observable property of the target class, with an identical name.</div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">- Each property of the auto-generated “ Foo_SwiftObservables” class would be an instance of a generic `Observable<T>` class, where `T` would be assigned to the value of the associated property of the target class.</div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">- The `Observable<T>` class would have two public functions: addObserver() and removeObserver(). </div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">- The addObserver() function would take a single closure argument. This closure would have a signature like: (oldValue: T?, newValue: T?) -> Void. </div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">- Observer closures would have the same memory management and variable scope rules as any other closure. Callers would not be obligated to remove their observer closures. Doing so would be a non-mandatory best practice.</div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><b>Rough Code Examples</b></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><b><br></b></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">Consider a class for a Twitter client like:</div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="color: rgb(0, 0, 0); margin: 0px;"><pre style="margin-top: 0px; margin-bottom: 1.65em; margin-left: -30px; word-wrap: normal; line-height: 1.41em; border: 1px solid rgb(221, 221, 221); background-color: rgb(240, 240, 240); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; box-sizing: border-box; padding: 14px; width: 600px; overflow: auto; color: rgb(64, 64, 64);"><font face="Courier"><span style="font-size: 12px;">class Tweet {
var isLiked: Bool = false
let text: String
init(text: String) {
self.text = text
}
}</span></font></pre></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">The compiler would generate a companion observables class:</div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="color: rgb(0, 0, 0); margin: 0px;"><pre style="margin-top: 0px; margin-bottom: 1.65em; margin-left: -30px; word-wrap: normal; line-height: 1.41em; border: 1px solid rgb(221, 221, 221); background-color: rgb(240, 240, 240); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; box-sizing: border-box; padding: 14px; width: 600px; overflow: auto; color: rgb(64, 64, 64);"><font face="Courier"><span style="font-size: 12px;">class Tweet_SwiftObservables {
let isLiked = Observable<Bool>()
}</span></font></pre></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">Notice that only the `isLiked` property is carried over, since the `text` property of `Tweet` is a let, not a var.</div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">The generic Observable class would be something like (pseudo-codish):</div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="color: rgb(0, 0, 0); margin: 0px;"><pre style="margin-top: 0px; margin-bottom: 1.65em; margin-left: -30px; word-wrap: normal; line-height: 1.41em; border: 1px solid rgb(221, 221, 221); background-color: rgb(240, 240, 240); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; box-sizing: border-box; padding: 14px; width: 600px; overflow: auto; color: rgb(64, 64, 64);"><font face="Courier"><span style="font-size: 12px;">class Observable<T> {
typealias Observer = (oldValue: T?, newValue: T?) -> Void
private var observers = [UInt: Observer]()
func addObserver(observer: Observer) -> Uint {
let token: Uint = 0 // generate some unique token
self.observers[token] = observer
return token
}
func removeObserverForToken(token: Uint) {
self.observers[token] = nil
}
}</span></font></pre></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><b>Benefits of This Approach</b></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><b><br></b></div><div id="bloop_customfont" style="margin: 0px;"><div id="bloop_customfont" style="margin: 0px;"><b>It’s familiar.</b> It resembles the core mechanic of KVO without the hassle. It uses existing memory management rules. Everything you already understand about closures applies here.</div><div id="bloop_customfont" style="margin: 0px;"><br></div><div id="bloop_customfont" style="margin: 0px;"><b>It’s type-safe.</b> The Observable<T> generic class ensures at compile-time that your observers don’t receive an incorrect type.</div><div id="bloop_customfont" style="margin: 0px;"><br></div><div id="bloop_customfont" style="margin: 0px;"><b>It’s readable. </b>The syntax is brief without being unclear. Implementing the observation closure at the same call site as addObserver() keeps cause and effect as close together as possible.</div><div id="bloop_customfont" style="margin: 0px;"><br></div><div id="bloop_customfont" style="margin: 0px;"><b>It’s easy.</b> It abandons a stringly-typed API in favor of a compile-time API. Since the Foo_SwiftObservables classes would be auto-generated by the compiler, there’s no need for busywork tasks like keeping redundant manual protocols or keyword constants up to date with the target classes.</div></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">Thanks for reading,</div><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div><div><br><div class="bloop_sign" id="bloop_sign_1451695005183772160"><div style="font-family:helvetica,arial;font-size:13px">-- <br>Jared Sinclair</div><div style="font-family:helvetica,arial;font-size:13px">@jaredsinclair<br>jaredsinclair.com</div></div></div></body></html>