When we pass an instance of an object as an argument to a method, which is defined inside that object like in the code below, does it create a retain cycle?
self.someMethod(self)
If you reference an instance method of your class:
class FooBar {
func foo() {
print("Foo")
}
func bar() {
self.foo()
}
}
The "self" is implied, and the compiler doesn't need it. Even if you use foo() without the self., it still references self implicitly.
No there is no retain cycle, since you are calling from one instance method to another instance method on the same object.
Where self gets you in trouble is in "escaping" closures. An escaping closure is a closure that is passed to another object that takes a strong reference to it. Completion handlers are usually escaping closures.
So, when you call a function that takes a completion handler and that completion handler is an escaping closure, the other object takes ownership of the closure.
Now, if the closure uses self, it means the closure also owns "self"
typealias completionHandler = () -> ()
class AClass {
lazy var someOtherObject = SomeOtherObject()
var value: Int = 0
func callClosure() {
someOtherObject.doSomething(completion: {
self.foo = self.foo + 1
}
}
}
class SomeOtherObject {
var myCompletionHandler: () -> ()
func doSomething( completion: @escaping completionHandler) {
myCompletionHandler = completion
//pretend there is code here to call an async method and invoke completion
}
}
The above use of self does cause a retain cycle. Here's how:
And instance of AClass creates an object of type SomeOtherObject and keep a strong/owning reference to it. The SomeOtherObject won't go away as long as the instance of AClass lives.
When we call callClosure(), we pass in a completion handler. The SomeOtherObject takes ownership of the closure.
Now, since the code of the closure references self, the closure takes ownership of self. We now have a 3-way retain cycle between the AClass object, which owns the SomeOtherObject, the SomeOtherObject, which owns the closure, and the closure, which owns the AClass object.
You can fix the retain cycle by adding a "capture list" to the closure. That is a list of variables that the closure uses that should be held weakly. That modified code might look like this:
func callClosure() {
//the `[weak self]` capture list below makes self weak inside the closure
someOtherObject.doSomething(completion: { [weak self] in
///use a guard to map weakSelf to strongSelf if it isn't nil, or return
//if self has been deallocated and is now nil.
guard let strongSelf = weakSelf else { return }
strongSelf.foo = strongSelf.foo + 1
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With