The cause of

An article… The reason is that the author found a memory leak while developing a self-test. At the end of the inspection, it was found that there was a strong reference to the ViewController in the code implemented with RxSwift, resulting in a circular reference. The code is written like the following:

class ViewController: UIViewController {

  private let a: PublishSubject<Int> = PublishSubject<Int>.init(a)private let bag: DisposeBag = DisposeBag.init(a)override func viewDidLoad(a) {
      super.viewDidLoad()
      // Do any additional setup after loading the view.
      a.asObserver().subscribe { event in
          print(event.element ?? 0)
          
          DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {[weak self] in
              print((self?.description ?? "") + "asyncAfter")
          }
      }.disposed(by: bag)
  }
  
  deinit {
      print(self.description + "deinit")}}Copy the code

And you end up with a ring. The solution is to declare the capture of the weak self on the closure of the SUBSCRIBE method. Given the length of the code, it’s possible that the closure didn’t use Self at the time, so it wasn’t explicitly declared. This is also a silly mistake by the author and an opportunity to fill in the knowledge blind spot of closure context capture.

Therefore, this paper will record:

  • SwiftContext capture logic for closures
  • Is it necessary to derive all closures from this[weak self].

Problem resolution

Analysis of the

a.asObserver().subscribe { event in
      print(event.element ?? 0)

      DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {[weak self] in
          print((self?.description ?? "") + "asyncAfter")
      }
}.disposed(by: bag)
Copy the code

To solve the problem I encountered, the code involves nesting two closures. The point of the leak was DispatchQueue. Main. AsyncAfter of closures in for self (ViewController instance) capture, led to the closure of the subscribe of outer also need to capture the self. Self does not declare weak in the outer closure, which results in the closure’s reference to self being strongly referenced.

  • ObservableType#subscribeThe closure of asEscape a closure

  • The GCD DispatchQueue# asyncAfterSo does the closure ofEscape a closure

This leads to the problem of the closure and self being indirectly referenced in a loop.

validation

Based on theaSend a signala.onNext(1)Trigger the closure described above. insubscribeSet a breakpoint at the closure ofAt this point, the variables accessible within the closure are:

  • Arguments to the closureevent
  • self

So if you change the code to:

a.asObserver().subscribe { event in
  print(event.element ?? 0)
  
  DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) { //[weak self] in
    // print((self? .description ?? "") + "asyncAfter")
      print("asyncAfter")
  }
}.disposed(by: bag)
Copy the code

Again, a signal a.onnext (1) is sent to trigger the closure described above. Put a breakpoint on the closure of SUBSCRIBE.

The only variables accessible within the closure are the closure’s event parameter.

Because GCD’s DispatchQueue#asyncAfter closure uses self, the outer closure implicitly captures self as well. The result is a very symmetrical ring.

Context capture of closures

Swift closure explicit capture

Swift closure implicit capture

Implicit capture

The problem I encountered is actually one of implicit capture. Here, borrowing an example from the documentation, is a breakpoint when the Incrementer closure is called.

func makeIncrementer(forIncrement amount: Int)- > () - >Int {
  var runningTotal = 0
  func incrementer(a) -> Int {
      runningTotal + = amount
      return runningTotal
  }
  return incrementer
}
Copy the code

The variables accessible in the closure are runningTotal and amount in the makeIncrementer method. Indicate that it implicitly captures these two variables.

There is another premise that caused the problem I encountered: the closure needs to be an escape closure. There is the following code:

private let b: Int = 1;

make {
  print(b)
}

func make(closure: () - >Void) {
  closure()
}
Copy the code
  • variablebFor the classThe global variable
  • makeMethod passes in a closureclosure.closureforNon-escape closure.

Print (b) inside the closure closure is equivalent to print(self.b). The reason is because the closure has implicitly captured self. If you make a few changes to the above code

private let b: Int = 1;

make {
  print(b)
}

func make(closure: @escaping() - >Void) {
  closure()
}
Copy the code

Reference to property ‘b’ in closure requires explicit use of ‘self’ to make capture semantics explicit. You need to use self.b to make the capture semantics explicit.

make { 
  print(self.b)
}
Copy the code

To be clear, self is implicitly captured in this way, whether it is escape or non-escape, but the compiler has a constraint on the escape closure that it must explicitly use self, presumably to discipline the developer to avoid cyclic references. Swift’s official documentation has a similar description.

The explicit capture

Explicit capture is easy to understand, such as the frequently-touched [weak self]. Of course, explicit capture can select the variables (not necessarily self) that you use in the closure, which are generally global variables. As in the previous example, it can be modified to

private let b: Int = 1;

make { [b] in
  print(b)
}

func make(closure: @escaping() - >Void) {
  closure()
}
Copy the code

The closure closure will then only capture the variable B, not self, unless you use self in the closure, which is again an implicit capture.

Back to the most common [weak self]. Explicit or implicit capture the following are equivalent:

// Implicit capture
make { 
  print(self.b)
}

// Explicit capture
make { [self] in
  print(self.b)
}
Copy the code

The meaning of weak self is to explicitly capture a weak-referenced self to ensure that it will not be recycled due to circular references to self held by the closure. Weak self: weak self: weak self: weak self: weak self: weak self: weak self: weak self

There is another [unowned self] besides [weak self], but this is a more dangerous usage. For reference:

Swift weak self and unowned self

What kind of closures are needed[weak self]?

This is a mistake, because when we were talking about escape closures, we got a compiler error asking us to explicitly call self. So it should go something like this:

  • Because a closure is declared to escape, it has the potential to be stored as a global variable by an object. (PS: Note that this is only possible).
  • Whether it isImplicit captureorexplicitActually captured, for exampleself. That isThe closure holds a reference to the object.
  • Because the captured object is not necessarilyselfSo it’s not necessarily a statement[weak self].Can be[weak abc].
  • Circular references are still the main culprit for memory leaks, so as long as references to closures and self (or any other global variable in self) are not looped, there should be no leaks.

Weak self: Weak self: Weak self: Weak self: Weak self: Weak self: Weak self: Weak self: Weak self: Weak self: Weak self: Weak self: Weak self: Weak self: Weak self: Weak self: Weak self: Weak self: Weak self: weak self: weak self: weak self: weak self: weak self: weak self: weak self

Make a qualified non-escape closure

We’ve already looked at why escape closures need to declare [weak XXX] when using self (or other global variables). Does that mean non-escape closures don’t have this problem? The answer is no. The reason is that it depends on the use of the closure, and the same is true if the non-escape closure is later passed to the escape closure.

let d = {
  print(self.b)
}
make(closure: d)

func make(closure: @escaping() - >Void){}Copy the code

In the code above, closure D is not a qualified non-escape closure.

conclusion

In this paper, the context capture problem of Swift closure is derived from a memory leak encountered by the author. The implicit and explicit capture of closures is briefly introduced. [weak self] is not suitable in all cases. You only need to explicitly use [weak self] to solve the problem of circular references if the closure holds a reference to the object as a global variable. Of course, the [weak self] does not have to be self, so you can narrow down the scope to capture as needed and optimize the release of the object.

Reference article:

An official documentation guide on Swift closure context capture

Swift closure + weak self