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?

asked Apr 21, 2016 at 8:36
1

2 Answers 2

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
answered Apr 21, 2016 at 8:53
Sign up to request clarification or add additional context in comments.

2 Comments

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

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.