Observing Properties in Swift

Property observing is neatly integrated into Swift, but I wanted to look a little bit beyond the basics (which I will cover), and look at some patterns for establishing external observers. To be honest, I'm not sure why support isn't built into the language (but I am also worried I've missed something)... Either way here is our first go at a solution that feels quite light-weight if a little pre-meditated.

So, let's do the easy things first. Observing a property of a class is as simple as providing one or two methods in the declaration of the property: willSet and didSet. As you might imagine, willSet is called before the property's value changes and didSet afterwards. Here's a simple example: 

class MyClass{

    var myProperty:String{

    //Called before the change
    willSet(newValue){
        println("MyClass.myProperty will change from "+myProperty+" to "+newValue)
    }
    
    //Called after the change
    didSet{
        println("MyClass.myProperty did change from "+oldValue+" to "+myProperty)
    }
    }
    
    //Note any initial change is not observed
    init(){
        myProperty = "First value"
    }
    
}

var myInstance = MyClass()
myInstance.myProperty = "Second value"

Rather nicely, you use this exact same pattern for normal local and global variables

var myString:String = "Hello"{ 
    willSet(newValue){
        println("myString will change from "+myString+" to "+newValue)
    }
    didSet{
        println("myString did change from "+oldValue+" to "+myString)
    }
}

myString = "Goodbye"

This is all well and good, but what if you want to observe a string property of another class? As I said in the introduction I feel like I'm missing something here, as I can't see language support for it. If we've missed it, please let us know!

So, here's a very specific class of an externally observable string

class ObservableString{
    typealias WillSet = (currentValue:String?,tobeValue:String?)->()
    typealias DidSet = (oldValue:String?,currentValue:String?)->()
    typealias Observer = (pre:WillSet,post:DidSet)
    
    var observers = Dictionary<String,Observer>()
    
    var stringValue:String? = nil{
    willSet(newValue){
        for (identifier,observer) in observers{
            observer.pre(currentValue: stringValue,tobeValue: newValue)
        }
    }
    didSet{
        for (identifier,observer) in observers{
            observer.post(oldValue: oldValue,currentValue: stringValue)
        }
    }
    }
    
    func addObserver(identifier:String, observer:Observer){
        observers[identifier] = observer
    }
    
    func removeObserver(identifer:String){
        observers.removeValueForKey(identifer)
    }
    
    init(initialValue:String?){
        stringValue = initialValue
    }
}

A few things here, we've taken some short-cuts (such as using tuples to capture the observer, and the actual observer methods are quite limited, but we wanted the pattern to be as clear as possible). 

We define a couple of blocks that are used by an Observer tuple to be called before and after the property change, and then define a property which when set runs through a dictionary of observers calling the supplied blocks. There is much that could be cleaned up and strengthened here but if we now create an instance and add an observer, it works quite nicely!

var willSetObserver : ObservableString.WillSet = {
    (currentValue:String?, tobeValue:String?) ->() in
        if currentValue && tobeValue{
            println("Will change from "+currentValue!+" to "+tobeValue!)
        } else if currentValue {
            println("Will change from "+currentValue!+" to nil")
        } else if tobeValue{
            println("Will change from nil to "+tobeValue!)
        }
    }


var didSetObserver : ObservableString.DidSet = {
    (oldValue:String?,currentValue:String?) -> () in
        if currentValue && oldValue{
            println("Did change from "+oldValue!+" to "+currentValue!)
        } else if currentValue {
            println("Did change from nil to "+currentValue!)
        } else if oldValue{
            println("Did change from "+oldValue!+" to nil")
        }
}
var genericStringObserver : ObservableString.Observer = (willSetObserver,didSetObserver)

var myObservableString = ObservableString(initialValue:"initalValue")
myObservableString.addObserver("Observer 1", observer:genericStringObserver)

myObservableString.stringValue = "Second Value"
myObservableString.stringValue = nil
myObservableString.stringValue = "Fourth Value"

This produces the following output

Will change from initalValue to Second Value
Did change from initalValue to Second Value
Will change from Second Value to nil
Did change from Second Value to nil
Will change from nil to Fourth Value
Did change from nil to Fourth Value

This is great, but we don't want to have to define a class for each type. Swift generics to the rescue!

class Observable<T>{
    typealias WillSet = (currentValue:T?,tobeValue:T?)->()
    typealias DidSet = (oldValue:T?,currentValue:T?)->()
    typealias Observer = (pre:WillSet,post:DidSet)
    
    var observers = Dictionary<String,Observer>()
    
    var observableProperty:T?{
        willSet(newValue){
            for (identifier,observer) in observers{
                observer.pre(currentValue: observableProperty,tobeValue: newValue)
            }
        }
        didSet{
            for (identifier,observer) in observers{
                observer.post(oldValue: oldValue,currentValue: observableProperty)
            }
        }
    }
    
    func addObserver(identifier:String, observer:Observer){
        observers[identifier] = observer
    }
    
    func removeObserver(identifer:String){
        observers.removeValueForKey(identifer)
    }    
}

Now we can just declare an instance and deal with the "one level" of indirection that the wrapping generic class introduces.

var myObservable : Observable<String> = Observable<String>("Hello, Swift")

The biggest issue here is that you need to know that you want the property to be observable when you write the class. As always, we are very keen to see other patterns OR have someone point out that you "just do it like this in Swift"