0

I've inherited a project that uses of ASIHttpRequest for all network communication. I am unclear as to which specific version we're using. All I can tell is that, from the .h files, the oldest creation date on a particular file is 17/08/10 (ASIDataDecompressor).

We're using completion and failure blocks. For some reason, the failure block is often triggered, which should only really happen if the server fails to respond. Our logs look sane, and we haven't received any notifications (Airbrake) that there were server problems around the time the errors occur, so for now I'm moving forward with the assumption that our server is fine and it's the app that is the culprit.

I decided to run the app through Instruments (Leaks) and was astonished to see that when I force a request to fail, ~27 leaks are created immediately. I'm don't know how to get around Instruments all that well, so I'm not really sure what to do with the information now that I have it.

I figured I'd post my code to see if there's anything glaring.

In viewDidLoad, this code is executed

[[MyAPI sharedAPI] getAllHighlights:pageNumber:perPage onSuccess:^(NSString *receivedString,NSString *responseCode) {
        [self getResults:receivedString];
        if(![responseCode isEqualToString:@"Success"]) {
            [self hideProgressView];
            appDelegate.isDiscover_RefreshTime=YES;                
            [[MyAPI sharedAPI] showAlert:responseCode]; 
        } else {
            NSString *strLogEvent=@"Discover_Highlights_Loaded Page_";
            strLogEvent=[strLogEvent stringByAppendingFormat:@"%i",intPageNumber];
            [FlurryAnalytics logEvent:strLogEvent timed:YES];
        }
    } onFail:^(ASIFormDataRequest *request) {
        NSDictionary *parameters = [[MyAPI sharedAPI] prepareFailedRequestData:request file:@"Discover" method:_cmd];
        [FlurryAnalytics logEvent:@"Unable_to_Connect_to_Server" withParameters:parameters  timed:true];
        [self hideProgressView];
        appDelegate.isDiscover_RefreshTime=YES;

        [[AfarAPI sharedAPI] showAlert:@"Unable to Connect to Server."];
        [tblHighlightsGrid reloadData];
        [tblListHighlights reloadData];

}];

These typedefs have been defined at the top of API Singleton:

typedef void (^ASIBasicBlockWrapper)(NSString *responseString,NSString *responseCode);
typedef void (^ASIBasicBlockWrapperFail)(ASIFormDataRequest *request);

MyAPISingleton#getAllHighlights...

- (void)getAllHighlights:(NSString *)pageNumber:(NSString *)perPage onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2{
    NSString *access_token= [[NSUserDefaults standardUserDefaults] objectForKey:@"access_token"];

    NSString *url = [baseURL stringByAppendingFormat:AFAR_GET_ALL_HIGHLIGHTS_ENDPOINT, pageNumber,perPage];
    if (access_token) { url = [url stringByAppendingFormat:ACCESS_TOKEN, access_token]; }

    __block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:url]];
    [request setRequestMethod:@"GET"];
    [request setDelegate:self];  
    [self executeAsynchronousRequest:request onSuccess:cb1 onFail:cb2];
}

And finally, MyAPI#executeAsynchronousRequest:

- (void) executeAsynchronousRequest:(ASIFormDataRequest *)request onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2
{
    [request setCompletionBlock:^{
        int statusCode = [request responseStatusCode];
        NSString *statusMessage = [self statusErrorMessage:statusCode];
        cb1([request responseString],statusMessage);
    }];
    [request setFailedBlock:^{
        cb2(request);
    }];

    [request startAsynchronous];    
}

Does anything stand out as to why 27 leaks are created?

djibouti33
  • 12,102
  • 9
  • 83
  • 116
  • Have you tried running the static analyzer? first stop for finding leaks. – Martin Apr 23 '12 at 08:04
  • I did, and I'm clear (unfortunately). Wish it was that easy. :) – djibouti33 Apr 23 '12 at 08:10
  • One thing I find useful for tracking down leaks in instruments is to switch to the call tree then check *Hide System Libraries* in the sidebar: http://cl.ly/3r3P1X1s0b0M1X1j3t14 – keegan3d Apr 23 '12 at 08:39

1 Answers1

0

I figured this out.

The ASIHttpRequest Documentation is very clear about the fact that you need to designate your request object with the __block storage mechanism:

Note the use of the __block qualifier when we declare the request, this is important! It tells the block not to retain the request, which is important in preventing a retain-cycle, since the request will always retain the block.

In getAllHighlights(), I'm doing that, but then I'm sending my request object as an argument to another method (executeAsyncRequest). The __block storage type can only be declared on local variables, so in the method signature, request is just typed to a normal ASIFormDataRequest, and so it seems as though it loses its __block status.

The trick is to cast (I'm not sure if that's technically accurate) the argument before using it in a block.

Here's my leak free implementation of executeAsyncRequest:

- (void) executeAsyncRequest:(ASIFormDataRequest *)request onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2
{
    // this is the important part. now we just need to make sure
    // to use blockSafeRequest _inside_ our blocks
    __block ASIFormDataRequest *blockSafeRequest = request;

    [request setCompletionBlock: ^{
        int statusCode = [blockSafeRequest responseStatusCode];
        NSString *statusMessage = [self statusErrorMessage:statusCode];
        cb1([blockSafeRequest responseString],statusMessage);
    }];

    [request setFailedBlock: ^{
        cb2(blockSafeRequest);
    }];

    [request startAsynchronous];    
}
djibouti33
  • 12,102
  • 9
  • 83
  • 116