Developer Sheldon
Developer Sheldon

Reputation: 2160

Swift guarding weak self for nested callbacks

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.

Upvotes: 7

Views: 3026

Answers (2)

Developer Sheldon
Developer Sheldon

Reputation: 2160

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.

Upvotes: 9

Andriy Gordiychuk
Andriy Gordiychuk

Reputation: 6272

If you capture strongSelf once it will be available in nested closures. However, this means that your object will not be deallocated. It may be good in some cases and bad in other ones. For example, consider that you call these nested closures from a view controller which lets people search for a city and pull a list of restaurants available in them and for the sake of the example let's assume that this is done with 2 nested asynchronous requests.

The first callback is called and self is captured. Now it will be retained until the second callback is called. But what if a user exits from your search controller? Do you want to prevent its deallocation because it is waiting for your second callback?

Basically, you just need to decide whether you want to capture your object between callbacks or not. If not, here is another possible alternative syntax which may be useful:

self?.someMethod()

Upvotes: 3

Related Questions