3

The logic is to clear an Array when it has a specified amount of elements. I could put the check outside of the Array but I was trying to see what if do it in Array's willSet event. The result is elements in Array stay still.

Here is the code

var MyArr=[String]() {
   willSet{
        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 conent is \(MyArr)")

MyArr was expected to have only one elements while actual result was four.

mfaani
  • 33,269
  • 19
  • 164
  • 293
Alan Hoo
  • 445
  • 3
  • 12
  • 1
    The value that gets passed in to `willSet` is a copy, modifying it won't affect the property value for value types. You would have to use `didSet` – dan Jul 23 '18 at 14:53
  • 1
    thanks dan....you mean that didSet has a reference value? – Alan Hoo Jul 23 '18 at 14:55
  • This is a good answer to the question of modifying a variable in it’s `willSet` method: https://stackoverflow.com/a/27810480 – Chris Jul 23 '18 at 18:35

3 Answers3

3

The behavior has nothing to do with value type / reference type

Please note the warning

Attempting to store to property 'MyArr' within its own willSet, which is about to be overwritten by the new value

which means that modifying the object in willSet has no effect.

vadian
  • 274,689
  • 30
  • 353
  • 361
2

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."]
dfrib
  • 70,367
  • 12
  • 127
  • 192
1

aAlan's comment on didSet vs willSet is interesting. I tried the same code but with didSet and it did remove the elements from the array which seemed odd initially. I think it's by design. My reasoning is:

  • if you actually did get a reference to the actual item itself inside willSet and made changes then everything would get overwritten. It makes all your changes nullified. Because you're doing this before you even read what's about to happen. Also (repeating what dfri has said) if you set it again inside willSet well then you're going to trigger the property observer again and again and will create a feedback loop which will crash the compiler, hence the compiler behind the scene creates a copy avoiding all this... will just suppress this mutation ie it won't allow it. It won't create a copy...simply put it would just ignore it. It does that by throwing a vague (because it doesn't really tell you that it will ignore the changes) warning:

enter image description here

  • same is not true with didSet. You wait...read the value and then make your decision. The value of the property is known to you.
mfaani
  • 33,269
  • 19
  • 164
  • 293
  • I was thinking about it in the same way as yours. But what about change of value triggers willSet/didSet again of the Array, which results into a infinite event loop? – Alan Hoo Jul 24 '18 at 13:49
  • @AlanHoo The statement about a recursive call (when modifying a property inside its property observer) in Honey's answer above is not true, as explained in my answer. _Arguably_, if this was the case, we could run into recursion, but it's not the case. Repeating the quoted part of the doc. in my answer: _This [modifying a property within its property observer] does not, however, cause the observer to be called again._. The compiler does not create a copy behind the scenes to avoid this, the language is just designed such that observers do not get invoked for mutation within an observer. – dfrib Jul 24 '18 at 15:15
  • @dfri thank you for the correction. I've made an edit. I feel like removing my answer, but I've decided to keep it since it's somewhat shorter/simpler than yours – mfaani Jul 24 '18 at 16:00