Where do I define captured references for nested closures in Swift?
Take this code as an example:
import Foundation
class ExampleDataSource {
var content: Any?
func loadContent() {
ContentLoader.loadContentFromSource() { [weak self] loadedContent in
// completion handler called on background thread
dispatch_async(dispatch_get_main_queue()) { [weak self] in
self?.content = loadedContent
}
}
}
}
class ContentLoader {
class func loadContentFromSource(completion: (loadedContent: Any?) -> Void) {
/*
Load content from web asynchronously,
and call completion handler on background thread.
*/
}
}
In this example, [weak self]
is used in both trailing closures, however the compiler is perfectly happy if I omit [weak self]
from either one of the trailing closures.
So that leaves me 3 options for defining my capture list:
My question is:
If I know that my
ExampleDataSource
could benil
at some point, what is the best option to go with?
It is important to note that GCD dispatch_async will NOT cause a retain cycle. In other words, when the block has finished executing, GCD will not retain any references made within the block.
The same is not true for strong references between classes, or strong references within a closure assigned to a property of an instance. Apple Documentation
That being said, in this example, the correct answer is option 2, to define captures on the first closure only.
For testing purposes I modified the code slightly:
class ExampleDataSource {
init() {
print("init()")
}
deinit {
print("deinit")
}
var content: Any?
func loadContent() {
print("loadContent()")
ContentLoader.loadContentFromSource() { [weak self] loadedContent in
dispatch_async(dispatch_get_main_queue()) {
print("loadedContent")
self?.content = loadedContent
}
}
}
}
class ContentLoader {
class func loadContentFromSource(completion: (loadedContent: Any?) -> Void) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) {
sleep(5) // thread will hang for 5 seconds
completion(loadedContent: "some data")
}
}
}
First I create, var myDataSource: ExampleDataSource? = ExampleDataSource()
.
Then I run myDataSource.loadContent()
.
Before the completion handler gets a chance to run, I set myDataSource = nil
, removing all references to it.
The debug console indicates that a reference to self was not retained:
init()
loadContent()
deinit
loadedContent
Looks like we found our answer! But just for completion's sake, let's test the alternatives...
If [weak self]
is instead captured on only the inner most trailing closure, GCD will retain ExampleDataSource
until the block has finished executing, which explains why the debug would instead look like this:
init()
loadContent()
loadedContent
deinit
The same thing will happen if no capture list is included and we never optionally unwrapped self
, although the compiler, does try to warn you!
While it isn't technically incorrect to include [weak self]
captures in all trailing closures, it does detract from the readability of code and doesn't feel very 'Swift-like'.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With