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.
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.