In my previous article Class Initializers, I had mentioned property observers, and glossed over them a bit. Now we’ll discuss them in a bit more depth.
Why use a Property Observer?
Back in Objective-C, if you wanted to do any special handling for setting a property, you would have to override the setter, reimplement the actual value setting (that was originally done for you), and then add whatever you wanted to do besides that, like posting a notification of a change. Swift’s property observers save you from having to reimplement the setter in those cases.
Property Observers are somewhat similar to computed properties. You can read more about those in my previous article Computed Properties in Swift. For computed properties, you write custom code for the getter and setter. For property observers, you write custom code only for setting, for right before (willSet) and right after (didSet). The main purpose of Swift’s property observers is to watch for when a property is set. As such, property observers are only useful for variables (var properties), and cannot be written for constants (let properties).
How to Implement Property Observers
There are two very appropriately named property observers in Swift: willSet, and didSet. They are called exactly when you think they would be, right before and right after setting, respectively. On top of this, they are also implicitly given constants describing the newValue and oldValue respectively.
- willSet comes with a constant parameter that contains the new value, with the default name newValue
- didSet comes with a constant parameter that contains the old value, with the default name oldValue
You can override either of those names, if you wish, but if no name is provided, the defaults are used.
Say we had a Swift String property. This property is set often in some program, but not always to a different value. If this String is set to the same value, just let things continue as if nothing happened (because really, not much did). If the String gets a new value though, we need to post a notification for another class to update based off of that new value. That is a great time to use Swift’s property observers!
Here is a declaration of such a Swift String with both property observers implemented with their default parameter names:
var userStatusText: String { willSet { print("About to set status to: \(newValue)") } didSet { if userStatusText != oldValue { postNewStatusNotification() } } }
This code uses the default names for the new and old value constants by not including a name of its own. In the willSet, we print out a line saying what we are about to change the status to. For the didSet, we check if the current value (since this is the didSet) of our variable is different from the oldValue that it used to be.
For reference, here is the same thing but with custom names for newValue and oldValue:
var userStatusText: String { willSet(incomingStatus) { print("About to set status to: \(incomingStatus)") } didSet(previousStatus) { if userStatusText != previousStatus { postNewStatusNotification() } } }
All you do to give it a custom name is write the new name inside parenthesis after the willSet or didSet, somewhat similar to naming parameters in a normal function, though without type information since the type is already stated as part of the property’s definition.
You do not have to define both property observers if you only need one. I am just showing both above for reference.
You can reassign to your value within didSet if you really want, and that new value you set will be the final value of the variable. Doing so does not recursively re-call the property observers. You might want to do this for some sort of bounds checking perhaps, though if that is a concern, a computed property might be a better choice, since there may be something the could react to the new value that wasn’t yet bounds checked during the few cycles that it exists in the invalid state (perhaps on a different thread). It should be called immediately, but I don’t think I would trust it if that new value needs to be bound checked. It would be best if we could bound-check in the willSet, test the newValue, and then set it to an appropriate value if it is out of bounds, but newValue is a constant so it cannot be written to. Perhaps something to fix in later versions of Swift? Maybe, but for now, if you have to bound check, use a computed property that bound-checks, and then writes to the actual property you are trying to set.
Property Observers are not called during Initialization
This was mentioned in my previous post, but I will mention it again here. In Swift, the property observers are NOT called during initialization or when a default value is set. I’m not sure if it sets the backing variable directly, or if there is a flag set that suppresses the property observers (the iBook seems to suggest the former), but either way, they are not called when variables are written to from an initializer or with a default value.
There is one caveat to this, if you have a class that subclasses another, and sets its SUPERclass’s properties in an initializer, the SUPERclass’s property observers will be called for those properties. Nonetheless, when you set properties in your own class from that own class’s initializer, the property observers are not called.
Properties that are told to lazy load (with the lazy keyword) cannot have property observers. I can think of a few reasons why this is probably the case. Firstly, you will set them out of an initializer, and then what would oldValue be for the didSet when you set it for the first time? The whole point of a lazy loaded property is to not set the variable up until its getter is called. That would probably leave the oldValue as nil, which would wreak havoc for non-optionals. Not to mention that the getter may not be called in the property observers, so, would the value even be written set since a lazy property doesn’t want to load until the getter tells it to? The more I think about it, the crazier adding property observers to lazily loaded properties becomes. Maybe a future version of Swift will get them in a way that makes sense, like just ignoring any sets done until the getter is called the first time, but for now, property observers on lazy-loaded properties make no sense, so don’t try it.
Conclusion
While property observers aren’t the biggest feature in Swift, they do have their place. They can be quite useful for value change notifications. They also can be used to keep related values in sync if you don’t want to use a computed property (such as if computing the related value each time it is requested was computationally expensive).
I hope you found this article helpful. If you did, please don’t hesitate to share this post on Twitter or your social media of choice, every share helps. Of course, if you have any questions, don’t hesitate to contact me on the Contact Page, or on Twitter @CodingExplorer, and I’ll see what I can do. Thanks!