Recently I was dropped into someone else’s codebase I’ve been able to tackle most things it’s thrown up so far but this one is a little over my head. There’s some retain cycles that I can’t figure out how to fix.
There is a custom object that wraps an FROAuthRequest, the FROAuthRequest has a completion block in which there are a further 3 blocks used, a parsing, finish and fail blocks. The completion, finish and fail blocks are all causing a retain cycle.
I know that the cause is references to ivars within the block but what I’ve tried hasn’t worked, see the end of the post for what I tried.
The following code is as it was before I started trying to fix it.The code path goes as follows:
1: Create the request:
//in the MainViewController.m
SHRequest *request = [api userInfo];
2: The method that creates the SHRequest
//in API.m
-(SHRequest*)userInfo{
FROAuthRequest *request = [[FROAuthRequest alloc]initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@%@",SH_URL_API,SH_URL_USER_INFO]]
consumer:consumer
token:token
realm:nil
signatureProvider:signatureProvider];
//wrap the FROAuthRequest in our custom object
//see below for the SHRequest
SHRequest *shRequest = [[SHRequest alloc]initWithRequest:request];
//set the parsing block
shRequest.parsingBlock = ^id(FROAuthRequest* finishedRequest){
NSDictionary *jsonResponse = [finishedRequest.responseData objectFromJSONData];
[user release];
user = [[SHUser alloc]initWithJSON:[jsonResponse objectForKey:@"user"]];
//more code
return [NSDictionary dictionaryWithObjectsAndKeys:user,kUserKey,nil];
};
[request release];
return [shRequest autorelease];
}
3: The SHRequest
//in SHRequest.m
-(id)initWithRequest:(FROAuthRequest*)_underlyingRequest{
if(self = [super init]){
underlyingRequest = [_underlyingRequest retain];
//this is the majority of the post processing
underlyingRequest.completionBlock = ^{
//if the requests fails call the fail block
if(underlyingRequest.responseStatusCode != 200){
if(failBlock != nil)
failBlock();
return;
}
if([underlyingRequest.responseData length] > 0){
[object release];
object = parsingBlock(underlyingRequest);
[object retain];
if((underlyingRequest.error || !object) && failBlock != nil)
failBlock();
else if(finishBlock != nil)
finishBlock();
}else if(failBlock != nil)
failBlock();
};
underlyingRequest.failedBlock = ^{
if(failBlock)
failBlock();
};
}
return self;
}
4: Once the SHRequest is returned from the userInfo method (1) the finish and fail blocks are set. (For this instance no failBlock is set.
//in MainViewController.m
request.finishBlock = ^{
NSDictionary *userInfo = request.object;
//User
SHUser *user = [userInfo objectForKey:kUserKey];
//more code
};
[request send];
Here’s what I have tried
I moved the completionBlock code to a method that starts the request and used __block types and the leaks appear to have disappeared but some of the __block vars are zombies when the completion block runs.
//in SHRequest.m
-(void)send{
__block void(^fail)(void) = failBlock;
__block void(^finish)(id) = finishBlock;
__block id(^parsing)(FROAuthRequest*) = parsingBlock;
__block FROAuthRequest *req = underlyingRequest;
underlyingRequest.completionBlock = ^{
if(req.responseStatusCode != 200){
if(fail != nil)
fail();
return;
}
if([req.responseData length] > 0){
id obj = parsing(req);//<--- parsing is a zombie
if((req.error || !obj) && fail != nil)
fail();
else if(finish != nil)
finish(obj);//<--- finish is a zombie
}else if(fail != nil)
fail();
};
underlyingRequest.failedBlock = ^{
if(fail)
fail();
};
[underlyingRequest startAsynchronous];
}
Any ideas on what I’m doing wrong?
Copying the parsing/finish/fail blocks and passing the request object as a param for the block seems to have sorted out my problem
and in any request where I need a reference to an ivar or the request itself I create a block var and retain it then release it inside the finish/fail blocks
This way Instruments reports no more leaks.