12

I need to keep strong self inside my inner clousures. I know that it's enough to declare [weak self] only once for outer closure.

But what about guard let self = self else { return }, is it enough to declare it once for outer closure also? Do we have any edge cases here ?

  apiManager.doSomething(user: user) { [weak self] result in
            guard let self = self else { return }

            self.storageManager.doSomething(user: user) { result in
                // guard let self = self else { return } <- DO WE NEED IT HERE ?
                self.doSomething()
            }
        }

Seems like language analyser says NO - one declaration is enough , but want to be sure.

pvllnspk
  • 5,667
  • 12
  • 59
  • 97
  • U need 2, sometimes, based on the solid example provided by https://stackoverflow.com/a/62352667/72437 . The safest approach, is to use 2, as those edge case is very hard to spot later on. – Cheok Yan Cheng Oct 10 '22 at 17:33

4 Answers4

12

Yes, one is enough. If you write

guard let self = self else { return }

you'll create a new local variable that will hold a strong reference to the outside weak self.

It's the same as writing

guard let strongSelf = self else { return }

and then use strongSelf for the rest of the block.

Andreas Oetjen
  • 9,889
  • 1
  • 24
  • 34
  • 1
    Yes, also including the nested blocks. In that case i personally would prefer `strongSelf`, because it's more exhaustive. – Andreas Oetjen Mar 03 '21 at 08:56
  • Yes, there is a difference, regarding retain cycles. But technically this will depend on how and how log the closure is being stored. – Andreas Oetjen Mar 03 '21 at 09:09
  • 2
    As of Swift 5.8 there are two additional simplifications: 1. it's possible now to write `guard let self else { return }`, 2. There is no need of explicitly writing `self.` inside the closure. Maybe worth an edit (?) – Kai Huppmann May 02 '23 at 13:28
9

Update Swift 5.7

You don't need a new guard in doSomething(user:), when that method works synchronously. With Swift 5.7, you can use the rewrite syntax guard let self else

apiManager.doSomething(user: user) { [weak self] result in
  guard let self else { return }

  self.storageManager.doSomething(user: user) { result in
      // You don't a new guard here, when doSomething works synchronously
      self.doSomething()
  }
}

But, when your guard only returns, than you could also write it so: Same behaviour.

apiManager.doSomething(user: user) { [weak self] _ in
  self?.storageManager.doSomething(user: user) { _ in
      self?.doSomething()
  }
}
SchmidtFx
  • 498
  • 4
  • 16
3

No, in short you do not need that. If you outer closure uses [weak self] then you should not worry about the inner closure as it will already have a weak reference to self. On the other hand if your outer closure is not using [weak self] it is reasonable to put it for the inside block. A more detailed explation can be found here.

πter
  • 1,899
  • 2
  • 9
  • 23
0

The safest approach is to use [weak self] 2 times in both outter and inner closure.

There is edge case, where using [weak self] 1 time will cause memory leak.

Consider the following escaping closure case, which keeps reference to closures (Example copied from https://stackoverflow.com/a/62352667/72437)

import UIKit

public class CaptureListExperiment {

    public init() {

    }

    var _someFunctionWithTrailingClosure: (() -> ())?
    var _anotherFunctionWithTrailingClosure: (() -> ())?

    func someFunctionWithTrailingClosure(closure: @escaping () -> ()) {
        print("starting someFunctionWithTrailingClosure")
        _someFunctionWithTrailingClosure = closure

        DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in
            self?._someFunctionWithTrailingClosure!()
            print("finishing someFunctionWithTrailingClosure")
        }
    }

    func anotherFunctionWithTrailingClosure(closure: @escaping () -> ()) {
        print("starting anotherFunctionWithTrailingClosure")
        _anotherFunctionWithTrailingClosure = closure

        DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [weak self] in
            self?._anotherFunctionWithTrailingClosure!()
            print("finishing anotherFunctionWithTrailingClosure")
        }
    }

    func doSomething() {
        print("doSomething")
    }

    public func testCompletionHandlers() {
        someFunctionWithTrailingClosure { [weak self] in
            guard let self = self else { return }
            self.anotherFunctionWithTrailingClosure {
                self.doSomething()
            }
        }
    }

    // go ahead and add `deinit`, so I can see when this is deallocated

    deinit {
        print("deinit")
    }
}

func performExperiment() {

    let obj = CaptureListExperiment()

    obj.testCompletionHandlers()
    Thread.sleep(forTimeInterval: 1.3)
}

performExperiment()

/* Output:

starting someFunctionWithTrailingClosure
starting anotherFunctionWithTrailingClosure
finishing someFunctionWithTrailingClosure
doSomething
finishing anotherFunctionWithTrailingClosure
*/

Since, this kind of edge case is very hard to spot. I think using [weak self] 2 times, will be the safest approach and common pattern, without causing developer much headache.

Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875