Skip to content

Instantly share code, notes, and snippets.

@4np
Last active May 15, 2020 08:21
Show Gist options
  • Save 4np/fd6981df487f5ed42426a9f07c1db32f to your computer and use it in GitHub Desktop.
Save 4np/fd6981df487f5ed42426a9f07c1db32f to your computer and use it in GitHub Desktop.
A Swift Playground demonstrating why you need to again weekly capture self in nested closures after unwrapping self.
import Cocoa
class TestClass {
var name = ""
var block1: (() -> Void)?
var block2: (() -> Void)?
var retainCount: Int {
CFGetRetainCount(self)
}
init() {
print("Initializing: \(retainCount)")
}
func foo() {
print("Starting Foo: \(retainCount)")
block1 = { [weak self] in
print("Inside block 1: \(String(describing: self?.retainCount))")
self?.block2 = {
// `self` is still weekly captured from `block1`.
print("Inside block 2: \(String(describing: self?.retainCount))")
}
// Execute block 2
self?.block2?()
print("Finishing block 1: \(String(describing: self?.retainCount))")
}
// Execute block 1
block1?()
print("Finishing Foo: \(retainCount)")
}
func bar() {
print("Starting Bar: \(retainCount)")
block1 = { [weak self] in
// Unwrapping `self`.
guard let self = self else { return }
print("Inside block 1: \(self.retainCount)")
// As you now have a strong reference to `self`, you to again weekly capture `self` in nested closures.
self.block2 = { [weak self] in
print("Inside block 2: \(String(describing: self?.retainCount))")
}
// Execute block 2
self.block2?()
print("Finishing block 1: \(self.retainCount)")
}
// Execute block 1
block1?()
print("Finishing Bar: \(retainCount)")
}
deinit {
print("Deinitializing: \(retainCount)")
}
}
var testClass: TestClass? = TestClass()
testClass?.foo()
testClass?.bar()
testClass = nil
@4np
Copy link
Author

4np commented May 15, 2020

In foo() you weekly capture self in block1, and you can just continue to use it in the nested closure block2. In bar() you weekly capture self in block1, but as you unwrap self you need to again weekly capture self in block2 as you now hold a strong reference to self.

Executing this playground demonstrates that, as in both cases TestClass is being properly released and TestClass deinitializes:

Initializing: 2
Starting Foo: 3
Inside block 1: Optional(4)
Inside block 2: Optional(4)
Finishing block 1: Optional(4)
Finishing Foo: 3
Starting Bar: 3
Inside block 1: 4
Inside block 2: Optional(5)
Finishing block 1: 4
Finishing Bar: 3
Deinitializing: 2

If you wouldn't weekly capture self in bar()'s block2, you would have a retain cycle because you are still holding a strong reference to self and the class is never deinitialized. Hence, a memory leak:

Initializing: 2
Starting Foo: 3
Inside block 1: Optional(4)
Inside block 2: Optional(4)
Finishing block 1: Optional(4)
Finishing Foo: 3
Starting Bar: 3
Inside block 1: 4
Inside block 2: 6
Finishing block 1: 5
Finishing Bar: 4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment