I’ve looked at some comments to questions on stackoverflow about using [weak self] and [unowned self]. I need to be sure I understand correctly.
I am using the latest Xcode – Xcode 13.4, the latest macOS – macOS Monterey 12.4, and I’m writing code compatible with iOS 12.0 to the latest iOS 15.
First of all, am I correct that memory that has a strong reference to it is owned by whichever the strong reference belongs to?
Also, and this is what I really need to know, I want to know what happens if I use [weak self] in
at the beginning of a closure, and then I have the statement guard let `self` = self else { return }
. I believe the guard statement, if the condition succeeds, assigns the reference self holds to the newly declared strong reference named self in the let `self
part of the guard statement. Is that correct?
What that leads me to ask is, What happens at the end of the closure. Am I correct that even though the newer self holds a strong reference, the memory the strong reference points to is deallocated once the code in the closure has executed the last statement in the closure, because the newer self with the strong reference is declared within the closure itself, and is deallocated along with all memory that had been allocated for that closure.
I think I got all of this right. If not, please let me know.
I am interested in any additional information or any clarifications if you will.
2
Answers
If the closure holds a strong reference, it prevents the ARC to release the object as long as the closure is still accessible.
The
weak
andunowned
self allow to resolve this strong ownership.But this means that the object referenced by the closure’s weak self could be deinitailized while the closure could still be called. In this case, the closure’s self would refer to nil.
The
guard
would in fact just check that the reference is still valid. A nil would make it fail anyway. If it’s a valid reference, it doesn’t change the capture list; you could just assume that self will stay valid until the current execution of the closure returns.Experimental proof
Here some code to experiment the difference, with a class that traces initialisation and deinitialization, and that can return a closure with
self
captured:Let’s create the dramatic circumstances where the closure could survive the instance that created it:
Let’s analyse what happens. The
f1()
will result in aT
instance that will be deinitialised. This is because the closure is no longer referenced when leavingf1
, so the capturedself
is no longer referenced either, and the ARC will terminate it:The
f2()
call is more subtle, because theT
instance is still referenced by the closure when leavingf2
, and the closure survives in a global variable.If the capture would be strong, you wouldn’t need the
guard
, since ARC would make sure that the instance is still available. You’d see that the T instance would not be deinitialized.But since the capture is
weak
, there is no strong reference to theT
instance when returning fromf2
and it would be deinitialized properly. So the guard would cause your to get:This proves that the
guard let self = self
does not replace the closure’s weak capture with a strong one.When you write a closure like this:
You are checking that the class in which this closure is declared is still allocated, if that class is still allocated you are creating a new strong reference to it inside the closure itself.
You do that because if all other strong reference to the class are deleted there will still be the strong reference created in
At that point you can be sure that until the end of the closure the class in which the closure is declared is allocated, because the reference is in the closure itself.
So, to answer your question, no, if you write
that closure can’t crash because you have a strong reference that’s still alive.
Different thing is if you use [unowned self]. Unowned self is like an unwrapped optional. If you use it without checking its existence you are implicitly writing
so if the self is deallocated when you call that closure it will crash.