skip to Main Content

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


  1. 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 and unowned 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:

    class T {
        var id:String
        init(_ name:String){
            id = name
            print ("INIT T object (id)")
        }
        func getClosure() -> (Int)->Int {
            return { [weak self] (i:Int) in    
                guard let self = self else {
                    print ("Instance vanished")
                    return -1
                }
                print (i,self.id)
                return 0
            }
        }
        deinit {
            print ("DEINIT T object (id)")
        }
    }
    

    Let’s create the dramatic circumstances where the closure could survive the instance that created it:

    var oops:(Int)->Int = {(Int)->Int in 0}
    
    func f1() {
        var t = T("f1_test")
        t.getClosure()(32)       // closure is disposed at the end of the function
    }                            // t will be deinitialized in any case
    func f2() {
        var t = T("f2_test")
        oops = t.getClosure()    // closure will be kept outside of scope
    }                            // t can be disposed only if capture is not strong
    
    f1()
    f2()
    oops(33)
    

    Let’s analyse what happens. The f1() will result in a T instance that will be deinitialised. This is because the closure is no longer referenced when leaving f1, so the captured self is no longer referenced either, and the ARC will terminate it:

    INIT T object f1_test
    32 f1_test
    DEINIT T object f1_test
    

    The f2() call is more subtle, because the T instance is still referenced by the closure when leaving f2, 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 the T instance when returning from f2 and it would be deinitialized properly. So the guard would cause your to get:

    INIT T object f2_test
    DEINIT T object f2_test
    Instance vanished
    

    This proves that the guard let self = self does not replace the closure’s weak capture with a strong one.

    Login or Signup to reply.
  2. When you write a closure like this:

    { [weak self] in
        guard let self = self else { return }
        ...
    }
    

    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

    guard let self = self else { return }
    

    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

    guard let self = self else { return }
    

    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

    self!.<something>
    

    so if the self is deallocated when you call that closure it will crash.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search