4

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?

bursyllac
  • 413
  • 1
  • 5
  • 11

1 Answers1

2

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
    }
}
Taras Chernyshenko
  • 2,729
  • 14
  • 27
  • Whoaa. It didn't cross my mind. It didn't make any sense either. Is there an explanation or is it just a bug? – bursyllac Oct 29 '18 at 16:02
  • 1
    @bursyllac it seems like a playground bug. I played with you code a little and this issue is not related to closures and memory management at all – Taras Chernyshenko Oct 29 '18 at 17:20