In my daily work I’m now involved in JavaScript mobile development but I also want to stay updated with Objective-C. During my train travels I read a lot but sometimes, due to the uproar or to my tiredness, I miss some important concepts. Currently I’m reading Effective Objective-C 2.0 by Matt Galloway. A book I really suggest to everyone. It is challenging and contains a lot of concepts.

A section of the above mentioned book talks about avoiding retain cycles introduced by block referencing the object owning them. In particular, the author introduces a subtle retain cycle that it is not immediate to grasp. The code looks like the following.

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

If you think in terms of object graph, the retain cycle is clear. The networkFetcher instance retains the block through a completionHandler copyied property (I’m skipping internal details here), while the block retains the networkFetcher since it uses it in NSLog. Breaking the retain cycle it means setting the completionHandler property to nil when the request has been fulfilled. The block will release the object it has captured, i.e. the networkFetcher, when it is deallocated - which happens when the completionHandler property is set to nil. So, within the NetworkFetcher class you need to just set as follows.

// in NetworkFetcher
- (void)requestCompleted {
    if(self.completionHandler) {
        // invoke the block with data
        self.completionHandler(self.downloadedData);
    }

    // break the retain cycle
    self.completionHandler = nil;
}

So, what about creating a self-retained network lib? As stated by Matt Galloway the code snippet contained in downloadData method is an approach that network libraries use to keep themselves alive. But here my question. What if I remove the line responsible for the retain cycle (i.e. the NSLog one)? Under ARC, a objc_release call will be inserted at the end of downloadData method. Hence, the request will not be performed. Since the author does not provide further details I’ve tried to think on it. How does this approach work? I ended up with the following code.

// NetworkRequest.h
typedef void(^NetworkRequestCompletionHandler)(NSData *data);

@interface NetworkRequest : NSObject

- (id)initWithURL:(NSURL*)url;
- (void)performWithCompletionHandler:(NetworkRequestCompletionHandler)completionHandler;

@end

// NetworkRequest.m
#import "NetworkRequest.h"

@interface NetworkRequest ()

@property (nonatomic, copy) NetworkRequestCompletionHandler completionHandler;
@property (nonatomic, strong) NetworkRequest *selfRef;
@property (nonatomic, strong) NSData *downloadedData;

@end

@implementation NetworkRequest

- (void)dealloc {
    NSLog(@"Hey!! NetworkRequest deallocated");
}

- (id)initWithURL:(NSURL*)url {
    if(self = [super init]) {
        _url = url;
    }
    return self;
}

- (void)performWithCompletionHandler:(NetworkRequestCompletionHandler)completionHandler {

    self.completionHandler = completionHandler;
//    self.selfRef = self;

      __weak NetworkRequest* weakSelf = self;
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
          [weakSelf internal_requestCompleted];
      });
}

- (void)internal_requestCompleted {

    NSLog(@"Hey!! NetworkRequest completed");

    if(self.completionHandler) {
        self.completionHandler(self.downloadedData);
    }

    self.completionHandler = nil;
//    self.selfRef = nil;
}

Within performWithCompletionHandler method I set the completionHandler for reusing it later and I create a sort of fake async request. Here, the using of weak is very important since the dispatch_after cannot retain self. Otherwise the experiment will fail (try your own). If now you run a NetworkRequest using the following code

// how to use NetworkRequest class
NSURL *urlRequest = [NSURL URLWithString:@"someurlhere"];
NetworkRequest *request = [[NetworkRequest alloc] initWithURL:urlRequest];
[request performWithCompletionHandler:^(NSData *data) {
    NSLog(@"Hello world!!!");
}];

you will notice that Hey!! NetworkRequest deallocated is logged as soon the app is run. This due to ARC. On the contrary, if you remove the comments to self.selfRet lines the code will work as expected.

A final note is that if you don’t use ARC pay attention to retain cycles…

Yep!! Goal achieved!!

Useful references: Effective Objective-C 2.0