Question

I'm studying a code snippet I grabbed from Effective Objective-C book by Matt Galloway. The snippet is the following (I've modified a little bit).

- (void)downloadData {
    NSURL *url = // alloc-init
    NetworkFetcher *networkFetcher =
        [[NetworkFetcher alloc] initWithURL:url];
    [networkFetcher startWithCompletionHandler:^(NSData *data){
        NSLog(@"Request URL %@ finished", networkFetcher.url);
        _fetchedData = data;
    }];
    // ARC will put a release call for the networkFetcher here
}

As stated by the author, such pattern is used by different networking libraries and there is a retain cycle. The retain cycle is quite obvious for me since, if you think in terms of object graph, the networkFetcher instance retains the block through a completionHandler property (copyied), while the block retains the networkFetcher since it uses it in NSLog.

Now, to break the block, the NetworkFetcher must set the completion handler to nil when it finishes to download the data has been requested.

// in NetworkFetcher.m class
- (void)requestCompleted {

    if(self.completionHandler) {
        // invoke the block
        self.completionHandler();
    }

    self.completionHandler = nil;
}

Ok. In this way there is no retain cycle anymore. The block, when run, it frees its reference to the networkFetcher and the networkFetcher makes nil the reference to the block.

Now, my question regards the execution flow of the snippet. Is the following sequence of actions correct?

  1. the networkFetcher runs the completion handler
  2. the block is executed
  3. the block frees the reference to the networkFetcher
  4. the networkFetcher release the reference to the block

My doubt relies on actions 3) and 4) . If 3) is executed before 4) no one has a reference to networkFetcher and so it can be released at any execution time (ARC will put a release call at the end of downloadData). Am I wrong or am I missing something?

Hope the question it's clear.

Was it helpful?

Solution

// in NetworkFetcher.m class
- (void)requestCompleted {

    if(self.completionHandler) {
        // invoke the block
        self.completionHandler();
    }

    self.completionHandler = nil;
}

The block is executed before it is set to nil. The execution of the block is synchronous in this method - nothing will happen until it has finished executing. Remember, the existence of a block does not mean that the code inside is going to be executed asynchronously.

The block doesn't free it's references once it is executed, because the block still exists as a property of the network fetcher instance. You could execute it again, if you were a bit strange.

The block only releases the objects it has captured when it is deallocated - which happens when the completionHandler property is set to nil, which is after the block has been executed.

OTHER TIPS

The steps are more like this:

  1. The network fetcher finishes the request and then calls the Block.
  2. The block executes

Nothing else happens. Since there is a retain cycle the network fetcher won't deallocate.

IFF the network fetcher explicitly sets its completion block ivar to nil after it called it, you get this:

Initially, the reference count of the completion block is +1.

  1. The network fetcher finishes the request and then calls the Block.
  2. The block executes asynchronously. (Release count of the block equals +1).
  3. The network fetcher sets the block to nil (release -1)
  4. The block finishes (release -1) and the block deallocates, and captured retainable variables get released (including the networkFetcher pointer).

There are a few other approaches to prevent a retain cycle:

- (void)downloadData {
    NSURL *url = // alloc-init
    NetworkFetcher *networkFetcher =
        [[NetworkFetcher alloc] initWithURL:url];
    [networkFetcher startWithCompletionHandler:^(NSData *data){
        NSLog(@"Request URL %@ finished", url);
        _fetchedData = data;
    }];
}


- (void)downloadData {
    NSURL *url = // alloc-init
    NetworkFetcher *networkFetcher =
        [[NetworkFetcher alloc] initWithURL:url];
    __block NetworkFetcher* blockNetworkFeatcher = networkFetcher;
    [networkFetcher startWithCompletionHandler:^(NSData *data){
        NSLog(@"Request URL %@ finished", blockNetworkFeatcher.url);
        _fetchedData = data;
        blockNetworkFeatcher = nil;
    }];
}

- (void)downloadData {
    NSURL *url = // alloc-init
    NetworkFetcher *networkFetcher =
        [[NetworkFetcher alloc] initWithURL:url];
    __weak NetworkFetcher* weakNetworkFeatcher = networkFetcher;
    [networkFetcher startWithCompletionHandler:^(NSData *data){
        NSLog(@"Request URL %@ finished", weakNetworkFeatcher.url);
        _fetchedData = data;
    }];
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top