Alexander Doloz
Alexander Doloz

Reputation: 4198

Retain cycle happens when passing method instead of closure

In Swift we can nice feature we didn't have in ObjC: it's possible to use a method everywhere you would use a closure. But it can lead to retain cycles. Look at this example:

import Foundation

class C1 {
    let closure: Void -> Void
    init(closure: Void -> Void) {
        self.closure = closure
    }

    deinit {
        print("C1 deinit")
    }
}

class C2 {
    var c1: C1!

    func initializeC1() {
        c1 = C1(closure: f)
    }

    func f() {}

    deinit {
        print("C2 deinit")
    }
}

func main() {
    let c2 = C2()
    c2.initializeC1()
}

main()

Here we created cycle C2 -> C1 -> f -> C2. If you run this program, deinit won't be called. But if you replace f in initializeC1 to {}, for example, it will be.

For regular closures we can use capture lists to avoid strong retaining but it looks like you can't use them for methods. So, the question is: How could we break retain cycle in such situation and is it possible at all?

Upvotes: 4

Views: 1487

Answers (2)

Retain cycle appear due to strong bounds of your variables.

Version 1. You can weak one of it:

class C2 {
    weak var c1: C1?

    func initializeC1() {
        c1 = C1(closure: f)
    }

    func f() {}

    deinit {
        print("C2 deinit")
    }
}

all other classes should be the same

Version 2. Change constant to variable:

class C1 {
    private var closure: (() -> Void)?
    init(closure: @escaping (() -> Void)) {
        self.closure = closure
    }
    func brokeRetainCycle(){
        self.closure = nil
    }
    deinit {
        print("C1 deinit")
    }
}

class C2 {
    var c1: C1?

    func initializeC1() {
        c1 = C1(closure: f)
    }
    
    func f() {}

    deinit {
        print("C2 deinit")
    }
}

func main() {
    let c2 = C2()
    c2.initializeC1()
    c2.c1?.brokeRetainCycle()
}

main()

Upvotes: 0

werediver
werediver

Reputation: 4757

Surely, we can "weakify" a bound method by wrapping it in closure like so:

import Foundation

class C1 {
    let closure: Void -> Void
    init(closure: Void -> Void) {
        self.closure = closure
    }

    deinit {
        print("C1 deinit")
    }
}

class C2 {
    var c1: C1!

    func initializeC1() {
        // HERE we wrap a method call into a closure to break retain-cycle.
        c1 = C1(closure: { [weak weakSelf = self] in
            weakSelf?.f()
        })
    }

    func f() {}

    deinit {
        print("C2 deinit")
    }
}

func main() {
    let c2 = C2()
    c2.initializeC1()
}

main()
//C2 deinit
//C1 deinit

Upvotes: 5

Related Questions