<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><blockquote type="cite" class="">On Jan 1, 2016, at 7:00 PM, Jared Sinclair via swift-evolution &lt;<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>&gt; wrote:<br class=""></blockquote><div><blockquote type="cite" class=""><br class="Apple-interchange-newline"><div class=""><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class="">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; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class="">What follows is a rough proposal for a Swift-native “KVO alternative”.&nbsp;</div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><b class="">What Usage Would Look Like:</b></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><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);" class=""><font face="Courier" class=""><span style="font-size: 12px;" class="">let tweet = Tweet(text: “Hi.”)
tweet.observables.isLiked.addObserver { (oldValue, newValue) -&gt; 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; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><b class="">Behind the Scenes:</b></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class="">- 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; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class="">- When initializing an instance of a Swift class, an instance of the companion “&nbsp;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; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class="">- The auto-generated “&nbsp;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; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class="">- Each property of the auto-generated “&nbsp;Foo_SwiftObservables” class would be an instance of a generic `Observable&lt;T&gt;` 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; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class="">- The `Observable&lt;T&gt;` class would have two public functions: addObserver() and removeObserver().&nbsp;</div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class="">- The addObserver() function would take a single closure argument. This closure would have a signature like: (oldValue: T?, newValue: T?) -&gt; Void.&nbsp;</div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class="">- 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; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><b class="">Rough Code Examples</b></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><b class=""><br class=""></b></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class="">Consider a class for a Twitter client like:</div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><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);" class=""><font face="Courier" class=""><span style="font-size: 12px;" class="">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; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class="">The compiler would generate a companion observables class:</div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><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);" class=""><font face="Courier" class=""><span style="font-size: 12px;" class="">class Tweet_SwiftObservables {
    let isLiked = Observable&lt;Bool&gt;()
}</span></font></pre></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class="">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; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class="">The generic Observable class would be something like (pseudo-codish):</div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><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);" class=""><font face="Courier" class=""><span style="font-size: 12px;" class="">class Observable&lt;T&gt; {
    typealias Observer = (oldValue: T?, newValue: T?) -&gt; Void
    private var observers = [UInt: Observer]()
     
    func addObserver(observer: Observer) -&gt; 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; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><b class="">Benefits of This Approach</b></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><b class=""><br class=""></b></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><div id="bloop_customfont" style="margin: 0px;" class=""><b class="">It’s familiar.</b><span class="Apple-converted-space">&nbsp;</span>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;" class=""><br class=""></div><div id="bloop_customfont" style="margin: 0px;" class=""><b class="">It’s type-safe.</b><span class="Apple-converted-space">&nbsp;</span>The Observable&lt;T&gt; generic class ensures at compile-time that your observers don’t receive an incorrect type.</div><div id="bloop_customfont" style="margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="margin: 0px;" class=""><b class="">It’s readable.<span class="Apple-converted-space">&nbsp;</span></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;" class=""><br class=""></div><div id="bloop_customfont" style="margin: 0px;" class=""><b class="">It’s easy.</b><span class="Apple-converted-space">&nbsp;</span>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; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class="">Thanks for reading,</div><div id="bloop_customfont" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; margin: 0px;" class=""><br class=""></div><div style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><br class=""><div class="bloop_sign" id="bloop_sign_1451695005183772160"><div style="font-family: helvetica, arial; font-size: 13px;" class="">--&nbsp;<br class="">Jared Sinclair</div><div style="font-family: helvetica, arial; font-size: 13px;" class="">@jaredsinclair<br class=""><a href="http://jaredsinclair.com/" class="">jaredsinclair.com</a></div></div></div><img src="https://u2002410.ct.sendgrid.net/wf/open?upn=kND2tqgLiolwf1-2Bhgg7fFiaPS455NT9j3CATwJCX70-2BTotp7vy7TF9cN03ArT7-2BQLHECv84S4OHmxpARpFlGM6thmf0AAJA4GtuQqd2riI-2BStKr1IiH8gpisoyxC3FLhqfqJzOr4HfG4KGGGWi90-2F13V-2FNtgC9MgP-2BTUMgM18tXMroPrY-2BnfocVy4Ug-2BihXMIVcU7KKcL3BT8f3aZbLln-2FMY7SaVS76RBZ9kne20-2F2w-3D" alt="" width="1" height="1" border="0" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; height: 1px !important; width: 1px !important; border-width: 0px !important; margin: 0px !important; padding: 0px !important;" class=""><span style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; float: none; display: inline !important;" class=""><span class="Apple-converted-space">&nbsp;</span>_______________________________________________</span><br style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><span style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; float: none; display: inline !important;" class="">swift-evolution mailing list</span><br style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><a href="mailto:swift-evolution@swift.org" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class="">swift-evolution@swift.org</a><br style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><a href="https://lists.swift.org/mailman/listinfo/swift-evolution" style="font-family: Helvetica, Arial; font-size: 13px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class="">https://lists.swift.org/mailman/listinfo/swift-evolution</a></div></blockquote></div><br class=""><div class="">I’ve been thinking about this for a while, as well. I think the problem would be better served by a simpler approach. Generating entire new classes is the way the existing KVO mechanism works, but I don’t really see the necessity in it.</div><div class=""><br class=""></div><div class="">Here’s my counter-pitch: add an “observable" keyword on property declarations, so your “isLIked” property would look like this:</div><div class=""><br class=""></div><div class="">class Tweet {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>observable var isLiked: Bool = false</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>let text: String</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>init(text: String) {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>self.text = text</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}</div><div class="">}</div><div class=""><br class=""></div><div class="">My first instinct is to make observations based on strings, as KVO does, as this is easier to integrate with XIB-based user interface elements, as well as making it possible for it to interact with the legacy KVO system. Using strings also makes it possible to bind to key paths, which can be useful. However, if one wanted to make the observations based on pointers rather than strings, for type safety, that would also be possible.</div><div class=""><br class=""></div><div class="">The strings would be provided by a parameter on the “observable” attribute (i.e. observable(“foo”)); if no parameter is provided, Swift would automatically insert the property name as the key.</div><div class=""><br class=""></div><div class="">When a property is marked “observable”, the Swift compiler rewrites the class to something resembling the following pseudocode:</div><div class=""><br class=""></div><div class=""><div class="">class Tweet {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>var isLiked_Observers: [(oldValue: Bool, newValue: Bool) -&gt; ()]? = nil</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>var isLiked: Bool {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>didSet(oldValue) {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>// optional for performance reasons; in the common case where there are no observers,</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>//&nbsp;checking an optional has less of a performance penalty than checking whether an array is empty.<span class="Apple-tab-span" style="white-space: pre;">                </span></div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>if let observers = self.isLiked_Observers {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                                </span>for eachObserver in observers {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                                        </span>eachObserver(oldValue: oldValue, newValue: self.isLiked)</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                                </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>let text: String</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>init(text: String) {</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>self.text = text</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>}</div><div class="">}</div></div><div class=""><br class=""></div><div class="">If there are no observers, the only cost added to the setter would be that of setting an optional.</div><div class=""><br class=""></div><div class="">What usage would look like:</div><div class=""><br class=""></div><div class=""><div class="">let tweet = Tweet(text: “Hi.”)</div><div class=""><br class=""></div><div class="">tweet.addObserverFor(“isLiked") { oldValue, newValue in</div><div class="">&nbsp; &nbsp; print(“Value changed.”)</div><div class="">}</div><div class=""><br class=""></div><div class="">tweet.isLiked = true // Console would log “Value changed.”</div></div><div class=""><br class=""></div><div class="">If isLiked later becomes a calculated property, it would add a “depedencies” attribute, which would return a set of other observable properties, like so:</div><div class=""><br class=""></div><div class=""><div class="">class Tweet {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>observable var numberOfLikes: Int = 0</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>observable var isLiked: Bool {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>dependencies { return ["numberOfLikes"] }</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>get { return !self.numberOfLikes.isEmpty }</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>set(isLiked) {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>if isLiked {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                                </span>self.numberOfLikes += 1</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>} else {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                                </span>self.numberOfLikes = 0 // or something; it’s just an example</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>let text: String</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>init(text: String) {</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>self.text = text</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>}</div><div class="">}</div></div><div class=""><br class=""></div><div class="">In this example, the “isLiked” property would generate an observation on “numberOfLikes” that would look something like the following. For this example, we introduce a cached version of the previous value of “isLiked" so that we have a copy of the old value in the case that the dependency changes (and isLiked’s willSet and didSet thus won’t fire). An alternative solution would be to run observation closures before *and* after setting each property, as KVO does; however, this would not perform as well, and would be more difficult to manage in multithreaded situations.</div><div class=""><br class=""></div><div class="">// generated by the compiler and added to initialization</div><div class="">self.addObserverFor(“numberOfLikes”) { [weak self] _, _ in</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>if let observers = self.observers {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>for eachObserver in observers {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>eachObserver(oldValue: isLiked_Cached, newValue: self.isLiked)</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>isLiked_Cached = self.isLiked</div><div class="">}</div><div class=""><br class=""></div><div class="">This would cause our notifications to be fired even for computed properties when one of the dependencies changes.</div><div class=""><br class=""></div><div class="">One final little perk would allow us to specify a dispatch queue upon which observations should be fired:</div><div class=""><br class=""></div><div class=""><div class="">class Tweet {</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>observable var isLiked: Bool = false {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>dispatchQueue { return dispatch_get_main_queue() }</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>let text: String</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>init(text: String) {</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>self.text = text</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>}</div><div class="">}</div></div><div class=""><br class=""></div><div class="">The benefits of this should not need explanation.</div><div class=""><br class=""></div><div class=""><b class="">Benefits of this approach:</b></div><div class=""><b class=""><br class=""></b></div><div class="">- It’s relatively simple and lightweight, with no additional classes being created, and the common case adding only the cost of an optional check to property setters.</div><div class=""><br class=""></div><div class="">- It’s easy to understand and to use.</div><div class=""><br class=""></div><div class="">- All observable properties are marked “observable” in the UI. As an example of why this is desirable, consider that you had been observing the “isLiked” property from the first example, and then the implementation of the Tweet class changed to the “numberOfLikes” implementation without the author considering KVO. Your observer code would break, and you would no longer get notifications for “isLiked” if “numberOfLikes” changed. Having an “observable” keyword communicates a contract to the user that this property will remain observable, and any changes will be made in a way that won’t break observability. Such contract is important to have if your client code is relying on the observation working properly.</div><div class=""><br class=""></div><div class="">- An additional benefit to only adding the observation code for properties that are explicitly marked “observable” is that the optional-check performance costs, as well as the memory allocation for the array of closures, can be skipped for properties that don’t need to be observable.</div><div class=""><br class=""></div><div class=""><b class="">Alternatives Considered:</b></div><div class=""><b class=""><br class=""></b></div><div class="">One could possibly base the observation around actual pointers, rather than strings. This would preclude any future extension to make this system interact with the existing KVO system, as well as necessitate some sort of rethinking about how bindings would be set up in XIB files, but it would add greater type safety (particularly, the compiler could enforce that observations could only be done on properties that were actually declared as observable).</div><div class=""><br class=""></div><div class="">One could introduce *two* arrays of observation closures, one to be fired before the setter runs and one afterward. This would work more similarly to how KVO works, but would introduce an extra performance cost, as each set would have to check *two* optionals rather than just one, and since the setter would have to wait for the “willSet” closure to finish before setting the property, which could slow down worker code. This would, however, eliminate the need for keeping a cached value for observable computed properties.</div><div class=""><br class=""></div><div class="">Charles</div><div class=""><br class=""></div></body></html>