4

I receive a memory warning when using UIImageJPEGRepresentation, is there any way to avoid this? It doesn't crash the app but I'd like to avoid it if possible. It does intermittently not run the [[UIApplication sharedApplication] openURL:url];

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *image = [info valueForKey:UIImagePickerControllerOriginalImage];
    NSData *imageToUpload = UIImageJPEGRepresentation(image, 1.0);

    // code that sends the image to a web service (omitted)
    // on success from the service
    // this sometime does not get run, I assume it has to do with the memory warning?
    [[UIApplication sharedApplication] openURL:url];
}
modusCell
  • 13,151
  • 9
  • 53
  • 80
Sam Luther
  • 1,170
  • 3
  • 18
  • 38
  • Are using ARC or non-ARC approach? – Oleg Gordiichuk Aug 11 '14 at 16:53
  • The problem is unlikely to be this single image, but rather this in conjunction with everything else you might be doing (e.g. if you are holding a lot of these images in memory at the same time). – Rob Aug 11 '14 at 17:33
  • @Rob I don't know about that, this is a pretty bare bones app and I've narrowed it down to that specific line of code that jumps the memory from about 6mb to over 50mb then a drop back to 6mb. I've also run the rest of the code without that specific line and no memory warning – Sam Luther Aug 11 '14 at 18:35
  • OK. Using 50mb is not outrageous amount of memory and shouldn't generally result in fatal errors, though I bet you'll be able to reduce the peak memory usage of the app, anyway. By the way, you say the `openURL` doesn't always run, but does the image upload successfully complete, regardless? I guess I'm asking whether you've pin pointed the problem as being `openURL` or possibly something before that... – Rob Aug 11 '14 at 20:46
  • @Rob sorry for the delayed response. Yes, the upload always completes but openURL doesn't – Sam Luther Aug 12 '14 at 20:59

3 Answers3

5

Using UIImageJPEGRepresentation (in which you are round-tripping the asset through a UIImage) can be problematic, because using a compressionQuality of 1.0, the resulting NSData can actually be considerably larger than the original file. (Plus, you're holding a second copy of the image in the UIImage.)

For example, I just picked a random image from my iPhone's photo library and the original asset was 1.5mb, but the NSData produced by UIImageJPEGRepresentation with a compressionQuality of 1.0 required 6.2mb. And holding the image in UIImage, itself, might take even more memory (because if uncompressed, it can require, for example, four bytes per pixel).

Instead, you can get the original asset using the getBytes method:

static NSInteger kBufferSize = 1024 * 10;

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    NSURL *url = info[UIImagePickerControllerReferenceURL];

    [self.library assetForURL:url resultBlock:^(ALAsset *asset) {
        ALAssetRepresentation *representation = [asset defaultRepresentation];
        long long remaining = representation.size;
        NSString *filename  = representation.filename;

        long long representationOffset = 0ll;
        NSError *error;
        NSMutableData *data = [NSMutableData data];

        uint8_t buffer[kBufferSize];

        while (remaining > 0ll) {
            NSInteger bytesRetrieved = [representation getBytes:buffer fromOffset:representationOffset length:sizeof(buffer) error:&error];
            if (bytesRetrieved <= 0) {
                NSLog(@"failed getBytes: %@", error);
                return;
            } else {
                remaining -= bytesRetrieved;
                representationOffset += bytesRetrieved;
                [data appendBytes:buffer length:bytesRetrieved];
            }
        }

        // you can now use the `NSData`

    } failureBlock:^(NSError *error) {
        NSLog(@"assetForURL error = %@", error);
    }];
}

This avoids staging the image in a UIImage and the resulting NSData can be (for photos, anyway) considerably smaller. Note, this also has an advantage that it preserves the meta data associated with the image, too.

By the way, while the above represents a significant memory improvement, you can probably see a more dramatic memory reduction opportunity: Specifically, rather than loading the entire asset into a NSData at one time, you can now stream the asset (subclass NSInputStream to use this getBytes routine to fetch bytes as they're needed, rather than loading the whole thing into memory at one time). There are some annoyances involved with this process (see BJ Homer's article on the topic), but if you're looking for dramatic reduction in the memory footprint, that's the way. There are a couple of approaches here (BJ's, using some staging file and streaming from that, etc.), but the key is that streaming can dramatically reduce your memory footprint.

But by avoiding UIImage in UIImageJPEGRepresentation (which avoids the memory taken up by the image as well as the larger NSData that UIImageJPEGRepresentation yields), you might be able to make considerably headway. Also, you might want to make sure that you don't have redundant copies of this image data in memory at one time (e.g. don't load the image data into a NSData, and then build a second NSData for the HTTPBody ... see if you can do it in one fell swoop). And if worst comes to worse, you can pursue streaming approaches.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • thank you very much for the in depth explanation, going to give this a shot – Sam Luther Aug 12 '14 at 21:03
  • BTW, with new Photos Framework, you can now use `requestImageDataForAsset` to get the asset's original `NSData`. See http://stackoverflow.com/a/27709329/1271826. – Rob Aug 12 '15 at 17:40
4

in ARC: Just put your code inside small block of @autoreleasepool

 @autoreleasepool {
     NSData *data = UIImageJPEGRepresentation(img, 0.5);
     // something with data
}
user3684669
  • 117
  • 1
  • 3
1

Presented as an answer for formatting and images.

Use instruments to check for leaks and memory loss due to retained but not leaked memory. The latter is unused memory that is still pointed to. Use Mark Generation (Heapshot) in the Allocations instrument on Instruments.

For HowTo use Heapshot to find memory creap, see: bbum blog

Basically the method is to run Instruments allocate tool, take a heapshot, run an iteration of your code and take another heapshot repeating 3 or 4 times. This will indicate memory that is allocated and not released during the iterations.

To figure out the results disclose to see the individual allocations.

If you need to see where retains, releases and autoreleases occur for an object use instruments:

Run in instruments, in Allocations set "Record reference counts" on (For Xcode 5 and lower you have to stop recording to set the option). Cause the app to run, stop recording, drill down and you will be able to see where all retains, releases and autoreleases occurred.

zaph
  • 111,848
  • 21
  • 189
  • 228