Memory management: retain cycle with weak var, non retain cycle with unowned. Why?

Viewed 149

TL;DR

I have a struct and a class. The struct has a reference to an instance of the class, and the object has a closure that captures the struct. If the reference to the object is unowned it seems that both of them get deinitialised. If the reference to the object is weak they retain each other. Why?

I have a struct and a class that may reference to each other and I was trying to figure out retain cycles and ways to break them. So I played a little bit with playground.

Given this code:

    struct A {
    unowned var b: B

    init(b: B) {
        self.b = b
    }

    func setup() {
        print("A setup")

        b.didSomethingClosure = {
            print("A: b did do something")
            self.printSomething()
        }
    }

    func printSomething() {
        print("A: A did do something")
    }
}

class B {

    var didSomethingClosure:(() -> Void)?

    func doSomething() {
        print("B: do something")
        didDoSomething()
    }

    func didDoSomething() {
        print("B: did something")
        if let closure = didSomethingClosure {
            closure()
        }
    }

    deinit {
        print("B: deinit")
    }
}


do {

    let b = B()
    let a = A(b: b)

    a.setup()
    b.doSomething()

    print("end do")
}

If var b in the struct is declared as unowned var b: B, the B object is released. If I modify the code to weak var b: B? and then b?.didSomethingClosure = ..., the B object is retained. Why?

1 Answers

I suppose problem is that you run it in Playground. Try to run it in real app and you will see that B is deallocated

struct A {
    weak var b: B?

    init(b: B) {
        self.b = b
    }

    func setup() {
        print("A setup")

        b?.didSomethingClosure = {
            print("A: b did do something")
            self.printSomething()
        }
    }

    func printSomething() {
        print("A: A did do something")
    }
}

class B {

    var didSomethingClosure:(() -> Void)?

    func doSomething() {
        print("B: do something")
        didDoSomething()
    }

    func didDoSomething() {
        print("B: did something")
        if let closure = didSomethingClosure {
            closure()
        }
    }

    deinit {
        print("B: deinit")
    }
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let b = B()
        let a = A(b: b)

        a.setup()
        b.doSomething()

        print("end do") // B is deallocated here
    }
}
Related