webo80
webo80

Reputation: 3393

Memory Leak in a dispatch_group

I'm having a problem with the (huge) amount of memory my app consumes after I do a concrete operation. Basically, it's a set of around 80 HTTP requests (*), that I must to wait all of them to finish, in a data sync operation. All is inside a NSOperation, that it's called by a NSOperationQueue. I receive the request, parse JSON, and save results in Core Data, nothing weird. The pseudo-code would be the following:

NSArray *idsToFetch = ...;
NSString *serverRequestFilter = ...;

//Suppose there are 80 batches, so here is one of them:
serverRequestFilter = [NSString stringWithFormat:---];

NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// <Setup some headers...>
NSURLSession *session = [NSURLSession sharedSession];

dispatch_group_enter(group);

[[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
  // <Some error handling here>

    id res = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
    if ([res isKindOfClass:[NSDictionary class]]) {
        NSDictionary *d = [(NSDictionary *)res objectForKey:@"d"];
        NSArray *results = [d objectForKey:@"results"];
        d = nil;
        res = nil;

        // <...>
        // Save in Core Data
        while (i < count) {
            [moc performBlockAndWait:^{
                Blablabla *bp;
                bp = (Blablabla *)[NSEntityDescription insertNewObjectForEntityForName:@"Blablabla" inManagedObjectContext:moc];

                NSDictionary *rel = results[i];                                    
                [bp setXXX:[rel valueForKey:@"XXX"]];
                // <the same for about 10 attributes> 
             }
         }

         // <Core data save>
         dispatch_group_leave(group);
    }
}] resume];

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

At this point, the memory consumption can be as high as 120Mb. I can continue using the app, put it in background, or whatever, I must have the mother of the memory leaks. If I leave this view controller, the memory consumption keeps high.

I profiled the app, seeing a huge amount of memory being leaked in strings (?), but I don't know how to fix it.

App memory profiling

Digging in the profile, the top 'responsible callers' from that strings are:

  - [NSPlaceholderString initWithFormat:locale:arguments:] (15k+)

  - [NSSQLCore _prepareDictionaryResultsFromResultSet:usingFetchPlan:] (15k+)

  - [NSURL(NSURL) initWithString:relativeToURL:] (the number of HTTP requests)

Thanks in advance

(*) due to server limitations, I must do this way. I must fetch a lot of entities, so I do it with pagination.

Upvotes: 0

Views: 1402

Answers (1)

Ankit Thakur
Ankit Thakur

Reputation: 4749

While working with blocks and memory management, please consider few points:

  1. Optimize memory with autorelease pool for heavy loops
  2. try to minimize CoreData perform block, as if we are using SQLite persistent store at the back of Core Data, each perform block works as a transaction and quite costly.
  3. Try to use weak reference of class level instance variables.

    dispatch_group_enter(group);
    
    [[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // <Some error handling here>
    
    id res = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
    if ([res isKindOfClass:[NSDictionary class]]) {
        NSDictionary *d = [(NSDictionary *)res objectForKey:@"d"];
        NSArray *results = [d objectForKey:@"results"];
        d = nil;
        res = nil;
    
        // <...>
        // Save in Core Data
        // CHANGE: optimize memory with autorelease pool
        @autoreleasepool{
            __block NSInteger localCount = count;
            [moc performBlockAndWait:^{
                // CHANGE: add while loop inside performBlock, because, perform block in loop on managedobjectcontext is quite costly
                while (i < localCount) {
                    Blablabla *bp;
                    bp = (Blablabla *)[NSEntityDescription insertNewObjectForEntityForName:@"Blablabla" inManagedObjectContext:moc];
    
                    NSDictionary *rel = results[i];                                    
                    [bp setXXX:[rel valueForKey:@"XXX"]];
                    // <the same for about 10 attributes> 
                 }
             }
    
         // <Core data save>
        }
         dispatch_group_leave(group);
    }
    }] resume];
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    

Upvotes: 1

Related Questions