为什么我不能在其 willSet 事件中从数组中删除元素?

Why can't I remove elements from an Array in its willSet event?

逻辑是当一个数组有指定数量的元素时清除它。我可以将检查放在数组之外,但我想看看如果在数组的 willSet 事件中执行它会怎样。结果是 Array 中的元素保持不动。

这是代码

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 预计只有一个元素,而实际结果是四个。

该行为与值类型/引用类型无关

请注意警告

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

表示修改willSet中的对象没有效果。

aAlan 关于 didSetwillSet 的评论很有趣。我尝试了相同的代码,但使用 didSet 并且它 did 从数组中删除了最初看起来很奇怪的元素。我认为这是设计使然。我的推理是:

  • 如果您确实在 willSet 中获得了对实际项目本身的引用并进行了更改,那么所有内容都会被覆盖。它使您的所有更改都无效。因为你在 before 这样做,所以你甚至阅读了即将发生的事情。另外(重复 dfri 所说的)如果你在 willSet 中再次设置它,那么你将再次触发 属性 观察者 并再次创建一个会使编译器崩溃的反馈循环,因此幕后的编译器 创建一个副本来避免所有这些... 只会抑制这种突变,即它不会允许它。它不会创建副本...简单地说它会忽略它。它通过抛出一个模糊的(因为它并没有真正告诉你它会忽略这些变化)警告来做到这一点:

  • didSet 则不同。你等着……阅读价值,然后做出决定。 属性 的值是您已知的。

引用 the Language Guide - Properties - Property Observers [强调 我的]:

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.

当您使用 willSet 属性 观察器进行实验时,您在 willSet 块中观察到的 属性 的任何突变 先于 紧跟在 willSet 块之后的 newValue 的实际存储。这意味着您实际上是在尝试在 myArr 的 "the old copy" 被替换为新值之前对其进行变异。

可以说这是非法的,因为 myArr 的任何突变都应该导致调用任何 属性 观察者,因此 属性 在 属性 观察者(引用类型的 引用 或值类型的 的变异)可能会导致递归调用 属性 观察者。然而,情况并非如此,对于 willSet 情况,具体而言,发出警告,如 . 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 指出 [emphasis我的]:

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.

这也是我们可以预料到的一种特殊情况,例如didSet 是合并的好地方,例如边界检查将给定的 属性 值限制在某个边界内;即,在前者超出范围的情况下,用有界值覆盖新值。

现在,如果在 存储新值后将 属性 更改为 ,则更改将生效,并且如上所述,不会触发对 属性 观察者的任何额外调用。应用于您的示例:

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."]