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:
Swift
Context 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#subscribe
The closure of asEscape a closure
The GCD DispatchQueue# asyncAfter
So 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 thea
Send a signala.onNext(1)
Trigger the closure described above. insubscribe
Set a breakpoint at the closure ofAt this point, the variables accessible within the closure are:
- Arguments to the closure
event
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
- variable
b
For the classThe global variable make
Method passes in a closureclosure
.closure
forNon-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 example
self
. That isThe closure holds a reference to the object. - Because the captured object is not necessarily
self
So 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