【问题标题】:Loops in didSetdidSet 中的循环
【发布时间】:2018-07-23 20:29:41
【问题描述】:

我们在 didSet 中使用循环时遇到了这种奇怪的行为。我们的想法是我们有一个具有树结构的数据类型,并且我们希望在每个元素中存储该项目所在的级别。所以在level 属性的didSet 中,我们还要设置孩子的级别属性。然而我们意识到,这只有在使用forEach 时才有效,而在使用for .. in 时无效。这是一个简短的例子:

class Item {

    var subItems: [Item] = []
    var depthA: Int = 0 {
        didSet {
            for item in subItems {
                item.depthA = depthA + 1
            }
        }
    }
    var depthB: Int = 0 {
        didSet {
            subItems.forEach({ $0.depthB = depthB + 1 })
        }
    }

   init(depth: Int) {
        self.depthA = 0

        if depth > 0 {
            for _ in 0 ..< 2 {
                subItems.append(Item(depth: depth - 1))
            }
        }
   }

   func printDepths() {
        print("\(depthA) - \(depthB)")

        subItems.forEach({ $0.printDepths() })
   }
}

let item = Item(depth: 3)
item.depthA = 0
item.depthB = 0
item.printDepths()

当我运行它时,我得到以下输出:

0 - 0
1 - 1
0 - 2
0 - 3
0 - 3
0 - 2
0 - 3
0 - 3
1 - 1
0 - 2
0 - 3
0 - 3
0 - 2
0 - 3
0 - 3

for .. in 循环调用时,它似乎不会调用 subItems 属性的 didSet。有谁知道为什么会这样?

更新: 问题不在于没有从 init 调用 didSet。之后我们更改属性(参见最后 4 行代码),两个深度属性中只有一个会将新值传播给子项

【问题讨论】:

标签: swift foreach for-in-loop didset


【解决方案1】:

如果您使用 defer 来更新任何可选属性或进一步更新您已经初始化的非可选属性,并且在您调用任何超级初始化方法之后,您的 willSet、didSet 等将被调用。

        for item in subItems {
            defer{
                item.depthA = depthA + 1
            }
        }

当您使用 forEach 时,它会与元素生成 kindOf “契约”,因为它是一个实例方法,与 for .. in 循环不同,它会触发 didSet 变量。上述情况适用于我们使用循环的情况,我们必须手动触发 didSet

这解决了我认为的问题。希望对你有帮助!!

【讨论】:

  • 谢谢!但是为什么我在for .. in 循环中需要这个,而在forEach 中却没有必要?
  • @M.Schrick 当您使用 forEach 时,它会使 kindOf 与元素“契约”,并且因为它是一个与 for .. in 循环不同的实例方法,它会触发变量的 didSet。上述情况适用于我们必须手动触发 didSet 的循环的情况。我希望这是有道理的?
  • 你手头有没有我可以阅读的链接?
  • @M.Schrick 因为它是一个高阶函数 (forEach) 以获得更多信息,您可以研究它作为参数的闭包。我想了解所有这些高阶函数的基础知识工作让我明白了。
【解决方案2】:

似乎在init 中,没有调用didSet。

在 Swift REPL 上试过这条线

class A { var a: Int { didSet { print("A") } }; init() { a = 5 } }

然后叫A()

没有调用didSet

但是

A().a = 7

发现调用了didSet

因此,解决方案是制作一个函数(最好是final),使您的效果需要放入didSet。然后从didSetinit 调用它。你可以把它放在你的课堂上。

final func updateA() {
    // Do your "didSet" here
}

var a: Int {
    didSet {
        updateA()
    }
}

init() {
    a = 5
    updateA()
}

所以在你的情况下:

func updateDepthA() {
    for item in subItems {
        item.depthA = depthA + 1
    }
}

var depthA: Int = 0 {
    didSet {
        updateDepthA()
    }
}

...

init(depth: Int) {
    self.depthA = 0
    updateDepthA()

    ...

【讨论】:

  • 问题不在于没有从init调用didSet。我们在初始化后设置了depth属性(见最后四行代码),如果我们使用for .. in循环,则不会调用子属性的didSet。
猜你喜欢
  • 2020-01-17
  • 2015-07-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-28
  • 2013-11-05
  • 2011-10-18
相关资源
最近更新 更多