Property observer is a special feature of Swift. By using it we can observe and make a response to the changes of properties. There are two types of methods to observe a property: willSet
and didSet
.
Adding them to a property is quite easy. By inserting the code block as below, we can observe the property and do whatever we want:
class MyClass {
var date: NSDate {
willSet {
print("Will set date from \(date) to \(newValue)")
}
didSet {
print("Did set date from \(oldValue) to \(date)")
}
}
init() {
date = NSDate()
}
}
let foo = MyClass()
foo.date = foo.date.dateByAddingTimeInterval(10086)
// Output:
// Will set date from 2014-08-23 12:47:36 +0000 to 2014-08-23 15:35:42 +0000
// Did set date from 2014-08-23 12:47:36 +0000 to 2014-08-23 15:35:42 +0000
We could use newValue
and oldValue
in willSet
and didSet
to represent the values which will be set or already been set respectively. A good example use of property observers would be validation of values. Let's say we do not want the date
to be later than one year from now in the code above, just change the didSet
and it would do the trick:
class MyClass {
let oneYearInSecond: NSTimeInterval = 365 * 24 * 60 * 60
var date: NSDate {
//...
didSet {
if (date.timeIntervalSinceNow > oneYearInSecond) {
print("It is too late.")
date = NSDate().dateByAddingTimeInterval(oneYearInSecond)
}
print("Did set date from \(oldValue) to \(date)")
}
}
//...
}
Now the date
is bounded in a year.
// 365 * 24 * 60 * 60 = 31_536_000
foo.date = foo.date.dateByAddingTimeInterval(100_000_000)
// Output
// Will set date from 2014-08-23 13:24:14 +0000 to 2017-10-23 23:10:54 +0000
// It is too late.
// Did set date from 2014-08-23 13:24:14 +0000 to 2015-08-23 13:24:14 +0000
The setting of properties in init method or by the observers themselves will not trigger the observing code. You can init your property and re-assign them without worry.
There are two types of property in Swift: stored property and computed property. Stored property will request memory allocation and there is a backend address for storing the value, while computed property is nothing more than the get
and set
functions. In the same class, property and computed property cannot exist at the same time. In other words, you can never write set
and willSet
/didSet
in the same class. Of course, we could change the code for set
to achieve the same behavior of willSet
and didSet
. But how could we do if we are not the owner of the code for setter? A workaround would be inheriting the class and implement the property observers in the child class, by overriding the property in the parent. The overridden property does not care about the detail of its parent:
class A {
var number :Int {
get {
print("get")
return 1
}
set {print("set")}
}
}
class B: A {
override var number: Int {
willSet {print("willSet")}
didSet {print("didSet")}
}
}
We can see how it works by calling `set` method of `number`
let b = B()
b.number = 0
// Output
// get
// willSet
// set
// didSet
There is nothing special about the order of set
and its observers. However, notice get
is also called once and it is before any other thing here. The reason is because we have implemented didSet
, in which oldValue
would be held. If we delete the didSet
code, the get
output would not exist either.