0

Okay, so I'm downloading a bunch of large-ish images (5mb) from a server in pieces, then stitching the pieces together and rendering the total image from a byte array. However, I've realized that the data for each image is not being released, and consequently builds up causing a memory warning and crash of my app. I thought that because of my explicit (__bridge_transfer NSData *) casting that ARC would take care of releasing the object, but it's still proving to be a problem. In instruments, objects called "CGDataProviderCopyData" of ~ 1mb build up and are not discarded for each file that is being stitched into the whole image. Any ideas or anyone who can steer me in the right direction? Much obliged.

 // Create  array to add all files into total image
NSMutableArray *byteArray = [[NSMutableArray alloc] initWithCapacity:(imageHeight * imageWidth)];

// Iterate through each file in files array
for (NSString *file in array)
{        
    // Set baseURL for individual file path
    NSString *baseURL = [NSString stringWithFormat:@"http://xx.225.xxx.xxx%@",[imageInfo objectForKey:@"BaseURL"]];

    // Specify imagePath by appending baseURL to file name
    NSString *imagePath = [NSString stringWithFormat:@"%@%@", baseURL, file];

    // Change NSString --> NSURL --> NSData
    NSURL *imageUrl = [NSURL URLWithString:imagePath];
    NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];

    // Create image from imageData
    UIImage *image = [UIImage imageWithData:imageData];
    CGImageRef cgimage = image.CGImage;

    size_t width  = CGImageGetWidth(cgimage);
    size_t height = CGImageGetHeight(cgimage);

    size_t bpr = CGImageGetBytesPerRow(cgimage);
    size_t bpp = CGImageGetBitsPerPixel(cgimage);
    size_t bpc = CGImageGetBitsPerComponent(cgimage);
    size_t bytes_per_pixel = bpp / bpc;

    // Get CGDataProviderRef from cgimage
    CGDataProviderRef provider = CGImageGetDataProvider(cgimage);

    // This is the object that is not being released
    NSData *data = (__bridge_transfer NSData *)CGDataProviderCopyData(provider);      //Using (__bridge_transfer NSData *) casts the provider to type NSData and gives ownership to ARC, but still not discarded

    const UInt8 *bytes = (Byte *)[data bytes];

    // Log which file is currently being iterated through 
    NSLog(@"---Stitching png file to total image: %@", file);

    // Populate byte array with channel data from each pixel
    for(size_t row = 0; row < height; row++)
    {
        for(size_t col = 0; col < width; col++)
        {
            const UInt8* pixel =
            &bytes[row * bpr + col * bytes_per_pixel];

            for(unsigned short i = 0; i < 4; i+=4)
            {
                __unused unsigned short red = pixel[i];         // red channel - unused
                unsigned short green = pixel[i+1];              // green channel
                unsigned short blue = pixel[i+2];               // blue channel
                __unused unsigned short alpha = pixel[i+3];     // alpha channel - unused

                // Create dicom intensity value from intensity = [(g *250) + b]
                unsigned short dicomInt = ((green * 256) + blue);

                //Convert unsigned short intensity value to NSNumber so can store in array as object
                NSNumber *DICOMvalue = [NSNumber numberWithInt:dicomInt];

                // Add to image array (total image)
                [byteArray addObject:DICOMvalue];
            }
        }
    }
    data = nil;
}
return byteArray;

Running "Analyze" through Xcode doesn't show any apparent leaks either.

1 Answers1

1

I took this code, almost verbatim, and did some more investigation. With the CFDataRef/NSData, I was able to see the problem you were seeing with the NSDatas not going away, and I was able to solve it by wrapping the portion of the code that uses the NSData in an @autoreleasepool scope, like this:

 // Create  array to add all files into total image
NSMutableArray *byteArray = [[NSMutableArray alloc] initWithCapacity:(imageHeight * imageWidth)];

// Iterate through each file in files array
for (NSString *file in array)
{        
    // Set baseURL for individual file path
    NSString *baseURL = [NSString stringWithFormat:@"http://xx.225.xxx.xxx%@",[imageInfo objectForKey:@"BaseURL"]];

    // Specify imagePath by appending baseURL to file name
    NSString *imagePath = [NSString stringWithFormat:@"%@%@", baseURL, file];

    // Change NSString --> NSURL --> NSData
    NSURL *imageUrl = [NSURL URLWithString:imagePath];
    NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];

    // Create image from imageData
    UIImage *image = [UIImage imageWithData:imageData];
    CGImageRef cgimage = image.CGImage;

    size_t width  = CGImageGetWidth(cgimage);
    size_t height = CGImageGetHeight(cgimage);

    size_t bpr = CGImageGetBytesPerRow(cgimage);
    size_t bpp = CGImageGetBitsPerPixel(cgimage);
    size_t bpc = CGImageGetBitsPerComponent(cgimage);
    size_t bytes_per_pixel = bpp / bpc;

    // Get CGDataProviderRef from cgimage
    CGDataProviderRef provider = CGImageGetDataProvider(cgimage);

    @autoreleasepool
    {
        // This is the object that is not being released
        NSData *data = (__bridge_transfer NSData *)CGDataProviderCopyData(provider);      //Using (__bridge_transfer NSData *) casts the provider to type NSData and gives ownership to ARC, but still not discarded

        const UInt8 *bytes = (Byte *)[data bytes];

        // Log which file is currently being iterated through 
        NSLog(@"---Stitching png file to total image: %@", file);

        // Populate byte array with channel data from each pixel
        for(size_t row = 0; row < height; row++)
        {
            for(size_t col = 0; col < width; col++)
            {
                const UInt8* pixel =
                &bytes[row * bpr + col * bytes_per_pixel];

                for(unsigned short i = 0; i < 4; i+=4)
                {
                    __unused unsigned short red = pixel[i];         // red channel - unused
                    unsigned short green = pixel[i+1];              // green channel
                    unsigned short blue = pixel[i+2];               // blue channel
                    __unused unsigned short alpha = pixel[i+3];     // alpha channel - unused

                    // Create dicom intensity value from intensity = [(g *250) + b]
                    unsigned short dicomInt = ((green * 256) + blue);

                    //Convert unsigned short intensity value to NSNumber so can store in array as object
                    NSNumber *DICOMvalue = [NSNumber numberWithInt:dicomInt];

                    // Add to image array (total image)
                    [byteArray addObject:DICOMvalue];
                }
            }
        }
        data = nil;
    }
}
return byteArray;

After adding that @autoreleasepool, I then commented out the part where you create NSNumbers and put them in the array, and I was able to see in the Allocations template of Instruments that indeed the CFData objects were now being released with each turn of the loop.

The reason I commented out the part where you create NSNumbers and put them in the array, is that with that code in there, you're going to end up adding width * height * 4 NSNumbers to byteArray. This means that even if the NSData was being released properly, your heap use would be going up by width * height * 4 * <at least 4 bytes, maybe more> no matter what. Maybe that's what you need to do, but it sure made it harder for me to see what was going on with the NSDatas because their size was being dwarfed by the array of NSNumbers.

Hope that helps.

ipmcc
  • 29,581
  • 5
  • 84
  • 147
  • I tried wrapping the for loop with autorelease but still doesn't release the data object – user2525045 Aug 29 '13 at 19:42
  • @user2525045 Edited with more investigation – ipmcc Aug 29 '13 at 20:55
  • not sure that I understand the last part of your comment about the heap growing no mater what. can you please clarify? – user2525045 Sep 03 '13 at 20:14
  • 1
    The code here causes memory to be allocated and grow at a rate greater than would be observed if the NSData objects were not being deallocated. Put differently, if having the NSData objects all simultaneously in memory would be enough to cause your app to be killed, then even after they're being properly released, you'd still be getting killed because you're creating another data structure from analyzing the image which is even bigger than the NSDatas themselves. – ipmcc Sep 03 '13 at 20:21