Imagining a First-Party Swift KVO Replacement

My last post got me thinking: if Apple were to add a KVO-like service to Swift, what would it look like? I wish I understood more about how programming languages are created, and how compilers work. What follows isn’t really a serious proposal, but just a sketch of what I would personally want to use as a practicing software developer.

What if every Swift class received an implicit, protected member with a reserved name like observables which provided a means to register closures as observers for changed values? An example usage might look like this:

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

The observables property would be implied, just like self. It would be an instance of some ad-hoc generated Swift class created by the compiler at compile time. For every observable property of the owning class, this observables class will have a corresponding observable property. So if you created a class like this:

class Tweet {
    var isLiked: Bool = false
    let text: String
    
    init(text: String) {
        self.text = text
    }
}

The Swift compiler would generate a class like this and assign it to the observables property for Tweet:

class Tweet_SwiftObservables {
    let isLiked = Observable<Bool>()
}

The Tweet_SwiftObservables class name would be auto-generated based on the class name of the target class. Notice that only the isLiked property is carried over, since the text property of Tweet is a let, not a var.

The isLiked member of Tweet_SwiftObservables is an instance of a generic Observable<T> class, whose implementation would be something like the following (though, of course, more nuanced):

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

The money shot is the addObserver() method. This method accepts a single Observer argument, which is just a simple closure. It would follow all the existing rules of memory management and variable scope as any other closure. addObserver() returns a unsigned integer token that can be used to remove the observer at a later time.

What I like about my idea is:

|  1 Jan 2016