Citing the Language Guide - Properties - Property Observers [emphasis mine]:
Property Observers
Property observers observe and respond to changes in a property’s
value.
...
You have the option to define either or both of these observers on a
property:
willSet
is called just before the value is stored.
didSet
is called immediately after the new value is stored.
As you are experimenting with the willSet
property observer, any mutation of the property you are observing within the willSet
block precedes the actual storing of the newValue
which follows immediately after the willSet
block. This means you are essentially attempting to mutate "the old copy" of myArr
prior to it being replaced with its new value.
Arguably this could be discussed as something illegal, as any mutation of myArr
should lead to the invocation of any property observers, thus mutation of a property within a property observer (mutation of the reference for reference types or the value for value types) could arguably lead to recursive calls to the property observer. This is not the case, however, and for the willSet
case, specifically, instead a warning is emitted, as pointed out in @vadian's answer. The fact that mutation of the property itself within a property observer will not trigger property observers is not really well-documented, but an example in the Language Guide - Properties - Type Properties points it out [emphasis mine]:
Querying and Setting Type Properties
...
The currentLevel
property has a didSet
property observer to check
the value of currentLevel
whenever it is set. ...
NOTE
In the first of these two checks, the didSet
observer sets
currentLevel
to a different value. This does not, however, cause
the observer to be called again.
This is also a special case we can somewhat expect, as e.g. didSet
is an excellent place to incorporate e.g. bounds checks that clamps a given property value to some bounds; i.e., over-writing the new value by a bounded one in case the former is out of bounds.
Now, if you change to mutate the property to after the new value has been stored, the mutation will take affect and, as covered above, not trigger any additional calls to the property observers. Applied to your example:
var myArr = [String]() {
didSet {
print("now count is: \(myArr.count)")
if myArr.count > 2 {
print("now remove all!")
myArr.removeAll()
}
}
}
myArr.append("hello")
myArr.append(",world")
myArr.append("!")
myArr.append("too much.")
print("The content is \(myArr)") // The content is ["too much."]