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.
EDIT:
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
}
}
Related
I have a retained cycle so my viewcontroller's deinit won't be called, and I'm trying to resolve this my adding [unowned self], but I'm not too sure where to put the unowned in my cases:
Case 1
class YADetailiViewController: UIViewController {
var subscription: Subscription<YAEvent>?
override func viewDidLoad() {
super.viewDidLoad()
if let query = self.event.subscribeQuery() {
self.subscription = Client.shared.subscribe(query)
self.subscription?.handle(Event.updated) {
query, object in
DispatchQueue.main.async {
[unowned self] in// Put unowned here won't break the cycle, but it does compile and run
self.pageViewLabel.text = String(object.pageViews) + " VIEW" + ((object.pageViews > 1) ? "S" : "")
}
}
}
}
}
Case 2
override func viewDidLoad() {
super.viewDidLoad()
if let query = self.event.subscribeQuery() {
self.subscription = Client.shared.subscribe(query)
self.subscription?.handle(Event.updated) {
[unowned self] query, object in // Put unowned breaks the cycle, and deinit is called
DispatchQueue.main.async {
self.pageViewLabel.text = String(object.pageViews) + " VIEW" + ((object.pageViews > 1) ? "S" : "")
}
}
}
}
I'm curious what's the differences between these two scenarios and why one works but not the other
Indeed, as correctly mentioned by #matt the problem is related to the time when self is captured. In fact int this code self is captured twice:
When the outer closure is passed to the handle method
When the inner closure is passed to the async method (during the handle closure execution)
The outer closure needs self to pass it to the inner closure, otherwise the inner closure won't be able to capture it.
The nature or retain cycle is the following: self(YADetailiViewController) -> subscription -> closure (handle parameter) -> self. To break this cycle it's enough to not retain self in that (outer) closure. That's why the second code sample works.
The second capture of self (in the inner closure) happens only when the outer closure is executed and lasts until that async block is executed - usually it's quite a short time.
In the first code sample you're not breaking the cycle. The second capture of self doesn't happen but the first one (causing thy cycle) is still present.
And if your handle closure can still be called when the view controller is already deinited/released then as suggested by #AdrianBobrowski you should use weak instead of unowned to prevent possible crash.
I have the following class, which uses a closure in one of its methods:
class SomeClass {
let someOtherClassInstance: OtherClass
func performAsyncTask() {
DispatchQueue.global(qos: .background).async { [weak self] in
print("\(self?.someOtherClassInstance)")
}
}
}
I'm wondering if I can also rewrite performAsyncTask as:
func performAsyncTask() {
let instance = self.someOtherClassInstance
DispatchQueue.global(qos: .background).async {
print("\(instance)")
}
}
The main goal is that I can avoid making self weak in the capture list - or rather so that I don't have to access self at all. There seems to be no reference to self in the second version, but is there a possibility that there will be an error when I try to access instance?
That's fine (assuming that self.someOtherClassInstance has no
back references to the SomeClass instance). You can achieve the
same with a capture list:
func performAsyncTask() {
DispatchQueue.global(qos: .background).async {
[instance = self.someOtherClassInstance] in
print("\(instance)")
}
}
The closure captures a strong reference to the
OtherClass instance which is held until it has been executed,
but no reference to self.
Note that the closure accesses instance regardless of whether
the SomeClass instance still exists or not, so the behavior is
slightly different from what your first method does.
In swift, I can use instance methods as closures, for example, assigning the method to a callback
self.someView.someCallback = self.doSomething
So, is self strongly referenced here in self.doSomething? Does the line above create a reference loop?
There are two possible scenarios based upon your code snippet:
If doSomething is a instance method of self, then, yes, that line establishes a strong reference. Remember that Closures are Reference Types. You can easily confirm this and is easily confirmed empirically. Consider:
class ViewController: UIViewController {
var foo: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
foo = bar
foo?()
}
func bar() { ... }
}
If I present and dismiss this view controller three times, and then use Xcode’s “Debug Memory Graph”, , I will see those three instances still lingering in memory (on the left) and if I select one, it will show me the strong reference cycle visually in the center panel:
And because I used the “Malloc stack” feature, on the right panel I can see precisely where the lingering strong reference is, namely in viewDidLoad where I set that closure.
However, if doSomething is not a function, but rather is a closure, then that line, itself, does not establish a strong reference, but rather it becomes a question of whether the closure, itself, refers to self and, if it does, whether there is a [weak self] or [unowned self] capture list or not. For more information, see Strong Reference Cycles for Closures.
In order to have a retain cycle, you need to have a strong reference on each direction, i.e.:
Object A strongly references Object B
Object B strongly references Object A
Assuming self in the code you shared is a View Controller, and assuming someView is a strong reference to a view, we could say that:
Object A (View Controller) strongly references Object B (Some View)
Now if Object B (Some View) has a strong reference back to the View Controller, you will have a retain cycle.
Assuming doSomething is a method in your ViewController, and not a closure, you will have a retain cycle
An easy way to check this, is by implementing deinit in both your Some View and your View Controller, like so:
class SecondViewController: UIViewController {
var someView: CustomView?
override func viewDidLoad() {
super.viewDidLoad()
someView = CustomView(frame: view.frame)
someView?.someCallback = doSomething
}
func doSomething() {
}
deinit {
print(#function)
}
}
final class CustomView: UIView {
var someCallback: (() -> Void)?
deinit {
print(#function)
}
}
You will see that the prints on deinit are never printed out in the console. However changing the way you assign someCallback to:
someView?.someCallback = { [weak self] in
self?.doSomething()
}
will cause deinit to run, thus breaking the retain cycle
Edit:
Or even, as an alternative:
weak var weakSelf = self
someView?.someCallback = weakSelf?.doSomething
(Even though this is using a weak reference, because this expression is evaluated at the time the assignment of someCallback is performed, not at the time it is executed, this will still become a strong reference) - Thanks #Rob
In Swift, declare a closure type variable, and would like to assign a func to it, prevent from the retain issue,
just do as follow, search the answer for all day long, eager to share:
self.someView.someCallback = {
[unowned self] in self.doSomething()
}
If I declare [weak self] on a closure and reference self as self? inside UIView.animateWithDuration the app will crash:
someFunc() { [weak self] (success) -> Void in
UIView.animateWithDuration(0.25) {
self?.someView.alpha = 1;
}
}
with a message sent to deallocated instance
but if I optionally unwrap self ahead of time it doesn't
someFunc() { [weak self] (success) -> Void in
if let weakself = self {
UIView.animateWithDuration(0.25) {
weakself.someView.alpha = 1;
}
}
}
Why is that, I would think that it doesn't matter which way I reference the weak self since it should "just" optionally unwrap self? correctly. For context this is done in a UICellView which is deallocated when I leave the UICollectionViewController
EDIT: Filed a bug with apple: #23492648
I think the problem here is that self is special. You've passed the reference to self weakly into the anonymous function to prevent a retain cycle, but there isn't really an Optional wrapping self in this story. Thus, the syntactic sugar self?.someView.alpha = 1 — and remember, it is merely syntactic sugar — doesn't work.
It may be that Apple will regard this as a bug; or maybe not. But either way, the solution is to do formulaically exactly what you are doing in the second example: do the weak-strong dance explicitly.
Swift Closure will have a strong reference cycle when it refers to self like this example:
class Test {
var name = "Hello"
func doSomething() {
{() -> Void in
self.name = "otherName"
}()
}
}
In the previous example, I created a strong reference cycle so I have to fix it with:
class Test {
var name = "Hello"
func doSomething() {
{[unowned self] () -> Void in
self.name = "otherName"
}()
}
}
Question: If I refer self in a closure do I have to use alway unowned self or are there cases where I have to use weak self?
If I refer self in a closure do I have to use alway unowned self or are there cases where I have to use weak self?
Neither. In most cases, just refer to self normally and do nothing with its memory management. You only have to worry about memory management if there is a danger of a retain cycle, and unless you store the closure somewhere, such as a property of self, there is no such danger.
You can easily prove this by adding a deinit implementation:
class Test {
var name = "Hello"
func doSomething() {
{() -> Void in
self.name = "otherName"
}()
}
deinit {
println("bye")
}
}
Now make a Test instance and immediately release it:
func testTest () {
let t = Test()
}
You see "bye" in the console, proving that the instance was released in good order. There was never any kind of "strong reference cycle" in this code. Your concerns are groundless.
[By the way, you are using the word "closure" wrong. Every Swift function is a closure. If there were a retain cycle issue merely because of using the word self in a closure, every Swift function would be subject to this issue - and clearly that is not the case. The place where weak and unowned self comes into play is in an anonymous function - and only, as I said before, if that anonymous function is itself also retained by self.]