Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loops in didSet

We came across this odd behaviour when using loops in a didSet. The idea was that we have a data type with a tree structure and in each element we wanted to store the level that item is on. So in the didSet of the level attribute we would also set the level attribute of the children. However we realised that this does only work if one uses forEach and not when using for .. in. Here a short example:

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()

When I run this I get the following output:

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

It seems like it will not call the didSet of the subItems attribute when it's called from an for .. in loop. Does anyone know why this is the case?

UPDATE: The problem is not that the didSet is not called from the init. We change the attribute afterwards (see last 4 lines of code) and only one of the two depth attribute will propagate the new value to the children

like image 453
M. Schrick Avatar asked Oct 16 '22 22:10

M. Schrick


1 Answers

If you use defer, for updating any optional properties or further updating non-optional properties that you've already initialized and after you've called any super init methods, then your willSet, didSet, etc. will be called.

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

When you use the forEach it makes kindOf "contract" with the elements and because it's an instance method unlike for .. in loop, it triggers the didSet of the variable. The above case applies where we use the loop, we have to trigger the didSet manually

This Solves the problem I think. Hope it helps!!

like image 177
Agent Smith Avatar answered Oct 21 '22 06:10

Agent Smith