4

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?

Alexander Doloz
  • 4,078
  • 1
  • 20
  • 36
  • 1
    I think it will be interested for you: https://sveinhal.github.io/2016/03/16/retain-cycles-function-references/ – Vitaly Berg Mar 01 '20 at 08:31

2 Answers2

5

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
werediver
  • 4,667
  • 1
  • 29
  • 49
  • 1
    Nice answer, but we came back to the closure syntax again which I tried to avoid. If there is no better solution it will be accepted answer. – Alexander Doloz Apr 21 '16 at 09:03
  • 1
    I don't see a clear statement in the docs, but it seems that methods are _strongly_ bound to their instances in Swift and we have no way to change this behaviour. I also don't see a way to make a nifty utility to overcome this limitation. – werediver Apr 21 '16 at 09:19
0

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