Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift guarding weak self for nested callbacks

Tags:

callback

swift

My question is more like better practice for the answer. Say we have multiple nested layers of callbacks, each layer we have to make self to be weak and I know we can write guard for each layer (see code snippet 1), but is this even necessary? If we only guard at the first layer should it be enough (see code snippet 2)?

If we think from the stand point of reference counting, will the first strongself be good enough?

Snippet 1:

let callBack1 = { [weak self] xx in 
  guard let strongSelf = self { return }
  // strongSelf.func(param)

  let callBack2 = { [weak self] yy in {
    guard let strongSelf = self { return }
    // strongSelf.func(param)

    let callBack3 = { [weak self] zz in
      guard let strongSelf = self { return }
      // strongSelf.func(param)
    }
  }
}

Snippet 2:

let callBack1 = { [weak self] xx in 
  guard let strongSelf = self { return }
  // strongSelf.func(param)

  let callBack2 = { [weak self] yy in {
    // strongSelf.func(param)

    let callBack3 = { [weak self] zz in
      // strongSelf.func(param) 
    }
  }
}

Note: This is a legit case in our code base, don't assume this will never happen.


Edit: To clarify the question, we assume each callback is happening asynchronously, and the self here is reference the current class (maybe a model, maybe a view controller) that can be release / pop during the any of the three call backs happens.

like image 619
Developer Sheldon Avatar asked Jul 26 '18 16:07

Developer Sheldon


People also ask

What does [weak self] mean when working with closures in Swift?

With everything you have learned so far, you can finally understand what [weak self] means when working with closures in Swift. In Swift, [weak self] prevents closures from causing memory leaks in your application. This is because when you use [weak self], you tell the compiler to create a weak reference to self.

Why are weak references optional in Swift?

For that reason, weak references must be optional. When the referenced object goes away, the compiler sets any weak reference pointing to it to nil. There is a particular type of reference cycle, very common in Swift code, that happens in escaping closures.

What is a self reference in a swift closure?

As you can see, in that closure, we have a reference to self. The Swift compiler forces you to make explicit any self reference in a closure so that it’s clear that capturing is happening. The TimeTracker keeps a reference to the Timer in its timer property. The Timer keeps a reference to the closure to execute it every time it fires.

How can I keep all code in a swift closure?

But if you want to keep all code in the closure, there is an alternative. The guard statement unwraps the self reference and assigns it to a let constant with the same name (in Swift, you can reuse some keywords as variable names using backticks). The following code then uses that constant, looking the same but without the unwrapping.


1 Answers

I think most part in @Andriy's answer is correct. But the most correct answer is that we don't even need to put [weak self] in any nested blocks.

When I first heard this from my colleagues, I don't want to buy it. But the truth is that, the first block defines the captured self to be weak and this captured self will be affecting all the captured self within current scope, in other words, within first block's {}. Therefore, there is no need to apply [weak self] any more if we have done it in the first most layer of callback.

It is very hard to find the exact document from Apple to prove this but the following code snippet with core foundation retain count CFGetRetainCount() it can prove that's true.

class TestClass {
    var name = ""
    var block1: (()->Void)?
    var block2: (()->Void)?

    func test() {
        print(CFGetRetainCount(self))
        self.block1 = { [weak self] in
            self?.block2 = { // [weak self] in
                print(CFGetRetainCount(self))
            }
            self?.block2?()
            print(CFGetRetainCount(self))
        }
        self.block1?()
        print(CFGetRetainCount(self))
    }

    deinit {
        print(CFGetRetainCount(self))
    }
}

do {
    let tstClass = TestClass()
    print(CFGetRetainCount(tstClass))
    tstClass.test()
    print(CFGetRetainCount(tstClass))
}

If you are interested, you can try this in your playground, feel free to remove the comment for the [weak self] for block2, you will see the same answer.

Retain count is always 2 and deinit is called correctly.

like image 137
Developer Sheldon Avatar answered Sep 21 '22 11:09

Developer Sheldon