Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct placement of capture list in nested closures in swift

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:

  1. define captures on every nested closure leading up to the reference
  2. define captures on the first closure only.
  3. define captures on only the most nested closure that actually uses the reference.

My question is:

If I know that my ExampleDataSource could be nil at some point, what is the best option to go with?

like image 238
Brandon Nott Avatar asked Oct 21 '15 16:10

Brandon Nott


1 Answers

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'.

like image 172
Brandon Nott Avatar answered Oct 22 '22 14:10

Brandon Nott