The beauty of Swift Language — Property Observer
Swift is one of my favourite computer languages. I would like to write a series of articles to demonstrate its beauty by comparing it with some other languages(C#, Kotlin) in areas like Property Observation , Property wrapper, Builders, Macros , Structured concurrency etc. This article , as the first of this series, will focus on the Property Observation.
About Property Observation
Property is a common concept in object-oriented programming languages. It is a member of an object and usually accessed using dot syntax. Sometimes, it can be very useful to observe the property value changes. For example, observing the balance property of a BankAccount object and logging out all the changes of the balance property could be very helpful to audit if all the transactions are recorded correctly in the BankAccount . Let’s take a look how this can be done in the following languages.
Swift
Swift offers two types of observations as below:
Stuct Account {
var balance: Double {
willSet {
// triggered before property value changes
// can access the new value from default parameter `newValue`
// or use willSet(parameterName) to explicilty specify parameter name to store the new value
print("balance will change from \(balance) to \(newValue)")
}
didSet {
// triggered after property value changes
// can access the old value from default parameter `oldValue`
// or use didSet(parameterName) to explicilty specify parameter name to store the old value
print("balance changed from \(oldValue) to \(balance)")
}
}
}
The willSet and didSet observers in Swift offers an easy and intuitive way to monitor the property value changes.
C#
In C#, no such concepts like willSet or didSet for properties. It offers INotifyPropertyChanged interface and PropertyChanged event to handle this like below:
public class Account: INotifyPropertyChanged
{
private double _balance = 0.0;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public double balance
{
get
{
return this._balance;
}
set
{
if (value != this._balance)
{
this._balance = value;
NotifyPropertyChanged();
}
}
}
}
As you can see, this implementation is very wordy comparing to the Swift syntax meanwhile it does not differentiate willSet and didSet and cannot handle newValue and oldValue of the property in the logic as well.
Another way to observe property change in C# is to use ObservableProperty attribute as below. This property allows us to implement some of the related methods to handle willSet and didSet logic.
[ObservableProperty]
private double balance = 0.0;
// Implement some of the following method to handle willSet and didSet loglic
// The method names are based on the property name.
partial void OnBalanceChanging(string? value);
partial void OnBalanceChanged(string? value);
partial void OnBalanceChanging(string? oldValue, string? newValue);
partial void OnBalanceChanged(string? oldValue, string? newValue);
Last option that can be considered is to raise event directly in setter block. However, doing this is not as clear semantically speaking.
Kotlin
In Kotlin, there is also no willSet and didSet concept. Similarly we can add some logic insetter like in C# to simulate(kind of) this behaviour like below:
var balance = 0.0
set(value) {
// willSet: add willSet logic here
field = value
// didSet: add didSet logic here
}
Another possible way of adding willSet/didSet logic is to utilize observable Delegate Property like below:
var balance: Double by Delegates.observable(0.0) { property, oldValue, newValue ->
// add observation logic here
}
This Delegates.observable can only observe the property value has changed though. If we really need to handle willSet and didSetlogic, we can subclass[ObservableProperty](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.properties/-observable-property/) and overwrite it’s beforeChange and afterChange method like below:
import kotlin.properties.ObservableProperty
import kotlin.reflect.KProperty
class ObservePropertyChange<T>(initialValue: T,
val willSet: (oldValue: T, newValue: T) -> Boolean,
val didSet: (oldValue: T, newValue: T) -> Unit) : ObservableProperty<T>(initialValue) {
override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean {
return willSet(oldValue, newValue)
}
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
didSet(oldValue, newValue)
}
}
Then we can use it like below:
class Account {
var balance1: Double = 0.0
set(value: Double) {
field = value
}
var balance: Double by ObservePropertyChange(0.0,
willSet = { oldValue, newValue ->
print("oldValue: $oldValue; newValue: $newValue")
true
}, didSet = { oldValue, newValue ->
print("oldValue: $oldValue; newValue: $newValue")
})
}
Conclusion
As you can see from above, property observation can be achieved in all these three languages. In comparison, the syntax of Swift for this goal is clearly the most intuitive and concise option.
Hooray, Swift is the obvious winner!!!
Sharing is caring!