iosobjective-cmemory-managementuiimageuiimagejpegrepresentation

Possible memory issue on UIImageJPEGRepresentation


I have a method that saves a bunch of UIImages as JPGs and my app is crashing, I believe due to memory not being released. I'm using UIImageJPEGRepresentation to save the images, and I'm wrapping it in an autorelease pool but it doesn't seem to be working.

       for (Images *anImage in images) {

            NSAutoreleasePool* p = [[NSAutoreleasePool alloc] init];

            NSString *fileName = [NSString stringWithFormat:@"%@-%i-%i-%i.jpg", appDelegate.currentUserName, appDelegate.reportId, aDefect.defectId, i];

            [UIImageJPEGRepresentation(anImage.image, 1) writeToFile:localImagePath atomically:NO];

            [p drain];

            i++;

        }

When I run the code above, the analyser shows me that more and more memory is being used, and it eventually crashes. If I remove the line - [UIImageJPEGRepresentation(anImage.image, 1) writeToFile:localImagePath atomically:NO]; it works fine.

FYI the loop iterates through an array of NSManagedObjects.

Any help would be appreciated! Thanks

Here is the new code as per suggestion -

- (void) convertImages {

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

NSEntityDescription *entity = [NSEntityDescription entityForName:@"Report" inManagedObjectContext:context];
NSPredicate *predicate = [NSPredicate
                          predicateWithFormat:@"status != %@", @"Leads"];

[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
[fetchRequest setIncludesPropertyValues:NO];

NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];

for (Report *aReport in fetchedObjects) {

    appDelegate.reportId = [aReport.reportId intValue];

    NSArray *defects = [self getAllItemDefects];

    for (ItemDefect *anItemDefect in defects) {

        NSArray *defectImages = [self getImages:[anItemDefect.itemDefectId intValue] isMainImage:NO];

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
        dispatch_async(queue, ^(void) {

            int i = 0;

            for (Images *anImage in defectImages) {

                NSString *fileName = [NSString stringWithFormat:@"%@-%i-%i-%i.jpg", appDelegate.currentUserName, [aReport.reportId intValue], [anItemDefect.itemDefectId intValue], i];

                NSString *localImagePath = [documentsDirectory stringByAppendingPathComponent:fileName];

                [UIImageJPEGRepresentation(anImage.image, 1) writeToFile:localImagePath atomically:NO];

                i++;

            }

            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"FINISH: do eventual operations");
            });

        });

    }

}

}

That gives me this error -

enter image description here

If I load the defectImages array within the dispatch block, it just does nothing. Thanks for your help.

Edit - As CoreData is not thread safe, I've declared new FetchRequests and ObjectContexts to fix that problem. I'm no longer getting the bad access error, but I'm back to running out of memory. I've simplified the code for you, but its producing a memory leak in this state -

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
        dispatch_async(queue, ^(void) {

            NSFetchRequest *backgroundFetchRequest = [[NSFetchRequest alloc] init];

            NSManagedObjectContext *backgroundContext = [[[NSManagedObjectContext alloc] init] autorelease];
            [backgroundContext setPersistentStoreCoordinator:persistentStoreCoordinator];
            NSEntityDescription *entity = [NSEntityDescription entityForName:@"Images" inManagedObjectContext:backgroundContext];
            NSPredicate *predicate = [NSPredicate
                                      predicateWithFormat:@"itemDefectId=%i AND reportId=%i AND isMainImage=%i", itemDefectId, appDelegate.reportId, NO];

            [backgroundFetchRequest setEntity:entity];
            [backgroundFetchRequest setIncludesPropertyValues:NO];
            [backgroundFetchRequest setPredicate:predicate];

            NSArray *defectImages = [backgroundContext executeFetchRequest:backgroundFetchRequest error:&error];

            NSMutableArray *images = [[NSMutableArray alloc] init];

            for (Images *anImage in defectImages) {
                [images addObject:anImage.image];
            }

            [images release];

            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"FINISH: do eventual operations");
            });
        });

Solution

  • This isn't the good solution, use GCD:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
    
    for (int i=0; i<[images count]; i++) {
    
        dispatch_async(queue, ^(void) {
    
            Images *anImage = (Images *)[images objectAtIndex:i];
            NSString *fileName = [NSString stringWithFormat:@"%@-%i-%i-%i.jpg", appDelegate.currentUserName, appDelegate.reportId, aDefect.defectId, i];
    
            [UIImageJPEGRepresentation(anImage.image, 1) writeToFile:localImagePath atomically:NO];
    
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"FINISH to write image %d", i);
            });
    
        });
    }
    

    Moreover, in your code, filename is ever the same, it doesn't change and isn't used. Your code is partial?

    However, use dispatcher for async process.