Search code examples
ioshttpautomatic-ref-countingnsurlrequest

ARC Async Block Best Practices


My application sends lots of messages via HTTP requests. I wrote a simple wrapper around an HTTP request that has a primary method to fire off a request and return any NSData as result.

It looks like this:

- (void)sendRequest:(NSURLRequest *)request {
    [[[NSURLSession sharedSession] dataTaskWithRequest:request
                                     completionHandler:^(NSData *data,
                                                         NSURLResponse *response,
                                                         NSError *error) {
                                         NSLog(@"---->> request returned");

                                         if (_delegate == nil)
                                             return;

                                         if (error == nil) {
                                             // forward along
                                             [_delegate requestReturnedResult:data withResponse:response];

                                         }
                                         else {
                                             // error occurred
                                             [_delegate requestReturnedError:error withResponse:response];
                                         }
                                     }] resume];
}

This HttpRequestHelper object is a ivar of a my message abstraction object OutboundMessage that packages up data and then sends it out.

I think have a MessageSender object that takes some data, creates a OutboundMessage object, and sends it out.

This abstraction works nicely except I think I'm bumping into an issue with ARC.

When someone calls [MessageSender send:], I alloc an OutboundMessage and send it out.

What I see via NSLog is that my OutboundMessage is cleaned up (presumably when send exits, then I see the HttpRequestHelper cleaned up. Then I see the NSLog in the completion handler of my dataTaskWithRequest.

This indicates to me that everything around my request helper was cleaned up prior to the HTTP request completing. Then as you can imagine, soon I try to bubble up my response via the delegate, I get exc_bad_access.

I can think of some creative ways to prevent the deallocation and prevent this but what I'd like to know is conceptually what are best practices for handling this pattern of firing off messages where the enveloping objects clean up only after the request is done.

Thanks.

Update Each wrapper object was using the initWithDelegate:self style patterns and I noticed those delegates were being saved like this:

@property (nonatomic, assign) id<HttpRequesterDelegate> delegate;

I changed to them to strong and it seems to correct it but I'm not sure if I'm leaking memory now or if there's a more optimal approach.


Solution

  • First of all, NSURLSessionDataTask object, that was created by NSURLSession - dataTaskWithRequest:completionHandler: method, retains completionHandler block object. The block object isn't released until the block is executed when the data task finished.

    Next, Blocks capture(retain) local variables automatically. The following line in the code

    if (_delegate == nil)
    

    is the same as

    if (self.delegate == nil)
    

    If delegate property is strong

    @property (nonatomic, strong) id<HttpRequesterDelegate> delegate;
    

    Then self is retained by the block. In this case, self is HttpRequestHelper object. So HttpRequestHelper object and delegate object live as long as the data task finished. It's safe.

    But delegate property is assign

    @property (nonatomic, assign) id<HttpRequesterDelegate> delegate;
    

    It is the same as __unsafe_unretained. Blocks doesn't retain __unsafe_unretained object at all. Thus self wasn't retained, it would be released before the data task finished.

    ADDED

    In this case, you can use a local variable to retain delegate object. Because the completionHandler uses only the delegate object, not HttpRequestHelper object itself.

    id<HttpRequesterDelegate> delegate = _delegate;
    
    [[[NSURLSession sharedSession] dataTaskWithRequest:request
                                     completionHandler:^(NSData *data,
                                                         NSURLResponse *response,
                                                         NSError *error) {
                                         NSLog(@"---->> request returned");
    
                                         /*
                                          * Use `delegate` instead of `_delegate`
                                          */
                                         if (delegate == nil)
                                             return;
    
                                         ...
    

    In this case, the attribute of delegate property is strong or assign, it doesn't matter at all.