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 (copy
ied), 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?
networkFetcher
runs the completion handlernetworkFetcher
networkFetcher
release the reference to the blockMy 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?
// 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.