Best way to approach a cycle between two classes and a closure at init?

Given the following scenario:

class A {
    let closure: () -> Void
    
    init(_ c: @escaping () -> Void) {
        self.closure = c
    }
}

class B {
    let other: A
    
    init(_ o: A) {
        self.other = o
    }
    
    func run() {
        other.closure()
    }
}

func create() -> B {
    let a = A {
        print("access B here")
    }
    let b = B(a)  
    return b
}

What would be the best way to implement the create() function? This is a scenario that I find myself finding over and over. Think of it as a ViewController/ViewModel relationship. I want to create the VC with a VM at init time, and the VM needs a closure at init time too. Everything is fine until you want to use the VC in the closure. The easy solution is to move the closure injection as a property, but then the VM (A in the example) needs to deal with optionals for no reason, from its prespective.

A solution is to declare a force unwrapped optional like this:

    var b: B!
    let a = A { // retain cycle
        print("access B here \(b.other)")
    }
    b = B(a)

but then you have a retain cycle since B -> A -> closrure -> B. And here are where the problems start. As soon as you weakify the capture then the value of b inside the closure is always nil.

    var b: B!
    let a = A { [weak b] in
        print("access B here \(b?.other)") // b is always nil
    }
    b = B(a)
  
    var b: B!
    let a = A { [unowned b] in
        print("access B here \(b?.other)") // b is always nil and why I need to unwrap now?
    }
    b = B(a)
  
    var b: B?
    let a = A { // still retain cycle
        print("access B here \(b?.other)")
    }
    b = B(a)

Is there an elegant solution that keeps A and B free of optionals? Am I missing something obvious?

Thanks

1 Like

[weak b] creates a new variable, which is initialized with the current value of b, but is not updated when b is changed.

Instead you want to capture and update the same variable. To express this you need to mark with weak variable declaration for b.

But having only weak b is not enough here, because after assignments to b and before the return statement there will be no strong references to b, and it will be reallocated. So we need an explicit strong reference, but it is not related to closure and can be let.

Final result:

weak var weakB: B?
let a = A {
    // use weakB
}
let b = B(a)
weakB = b
return b
2 Likes

That makes sense. I didn't think about using weak at the variable level.
Thanks