Reputation: 33428
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?
Hope the question it's clear.
Upvotes: 0
Views: 327
Reputation: 119292
// 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.
Upvotes: 3
Reputation: 19154
The steps are more like this:
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.
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;
}];
}
Upvotes: 1