[swift-evolution] Swift-Native Alternative to KVO

Jared Sinclair desk at jaredsinclair.com
Fri Jan 1 19:00:46 CST 2016


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.

What follows is a rough proposal for a Swift-native “KVO alternative”. 


What Usage Would Look Like:

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.”

Behind the Scenes:

- When compiling a Swift class “Foo", the compiler would also generate a companion “Foo_SwiftObservables” class.

- 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`.

- The auto-generated “ Foo_SwiftObservables” class would have a corresponding property for every observable property of the target class, with an identical name.

- 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.

- The `Observable<T>` class would have two public functions: addObserver() and removeObserver(). 

- The addObserver() function would take a single closure argument. This closure would have a signature like: (oldValue: T?, newValue: T?) -> Void. 

- 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.


Rough Code Examples

Consider a class for a Twitter client like:

class Tweet {
    var isLiked: Bool = false
    let text: String
     
    init(text: String) {
        self.text = text
    }
}
The compiler would generate a companion observables class:

class Tweet_SwiftObservables {
    let isLiked = Observable<Bool>()
}
Notice that only the `isLiked` property is carried over, since the `text` property of `Tweet` is a let, not a var.

The generic Observable class would be something like (pseudo-codish):

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
    }
}

Benefits of This Approach

It’s familiar. It resembles the core mechanic of KVO without the hassle. It uses existing memory management rules. Everything you already understand about closures applies here.

It’s type-safe. The Observable<T> generic class ensures at compile-time that your observers don’t receive an incorrect type.

It’s readable. 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.

It’s easy. 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.


Thanks for reading,


-- 
Jared Sinclair
@jaredsinclair
jaredsinclair.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160101/017e50e4/attachment.html>


More information about the swift-evolution mailing list