简体   繁体   English

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

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

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. 我可以将检查放在Array之外,但是我试图在Array的willSet事件中查看该怎么办。 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. 预期MyArr仅包含一个元素,而实际结果为四个。

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 尝试将属性“ MyArr”存储在其自己的willSet中,该属性将被新值覆盖

which means that modifying the object in willSet has no effect. 这意味着在willSet中修改对象无效。

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 . 在存储值之前调用willSet
  • didSet is called immediately after the new value is stored . 存储新值后,将立即调用didSet

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. 当您使用willSet属性观察器进行实验时,在willSet块中观察的任何属性突变willSetnewValue的实际存储之前发生,而newValue的实际存储紧随willSet块之后。 This means you are essentially attempting to mutate "the old copy" of myArr prior to it being replaced with its new value. 这意味着您实际上是在尝试将myArr “旧副本”突变为新值之前。

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. 可以说,这可能是一些非法的讨论,作为任何突变myArr按理说应该导致任何财产观察员的调用,因此性能观察者(引用类型的引用或值类型的值的突变)内的财产的突变可能导致对属性观察者的递归调用。 This is not the case, however, and for the willSet case, specifically, instead a warning is emitted, as pointed out in @vadian's answer . 但是,情况并非如此,对于willSet情况,尤其是,发出警告,如@vadian的答案中指出的那样。 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. currentLevel属性具有didSet属性观察器, currentLevel在设置currentLevel时检查其值。 ... ...

NOTE 注意

In the first of these two checks, the didSet observer sets currentLevel to a different value. 在这两个检查的第一个中, didSet观察器将currentLevel设置为不同的值。 This does not, however, cause the observer to be called again. 但是,这不会导致再次调用观察者。

This is also a special case we can somewhat expect, as eg didSet is an excellent place to incorporate eg bounds checks that clamps a given property value to some bounds; 这也是我们可以预料到的一种特殊情况,例如didSet是合并例如将给定属性值限制在某些范围内的范围检查的绝佳场所。 ie, 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."]

aAlan's comment on didSet vs willSet is interesting. aAlan对评论didSet VS willSet很有趣。 I tried the same code but with didSet and it did remove the elements from the array which seemed odd initially. 我尝试了相同的代码,但是使用didSet ,它确实从数组中删除了看起来很奇怪的元素。 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. 如果您确实在willSet确实获得了对实际项目的引用并进行了更改,则所有内容都将被覆盖。 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. 另外(重复dfri所说的内容),如果再次在willSet设置它,那么将一次又一次触发属性观察器并创建一个反馈循环,这将使编译器崩溃,因此,幕后的编译器将创建一个副本避免所有这些...只会抑制这种突变,即不允许它。 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: 它通过抛出一个模糊的警告(因为它并没有真正告诉您它将忽略更改)来做到这一点:

在此处输入图片说明

  • same is not true with didSet . didSet并非如此。 You wait...read the value and then make your decision. 您等待...读取值,然后做出决定。 The value of the property is known to you. 您知道该属性的值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM