0

I have an OSX Cocoa ARC project that handles thousands of images. In order to reduce the number of images, we scan the images to see if they are a uniform color, and if so, we discard the image and keep track of its color.

The code to get a solid color is:

- (NSColor *)getSolidColor:(NSImage *)image
{
NSBitmapImageRep* raw_img = [NSBitmapImageRep imageRepWithData:[image TIFFRepresentation]];

int Bpr = [raw_img bytesPerRow],spp = [raw_img samplesPerPixel];
unsigned char *data = [raw_img bitmapData];
int w = [raw_img pixelsWide],h = [raw_img pixelsHigh];

uint32_t mask = 0x00FFFFFF;
uint32_t color = *((uint32_t *)data) & mask;

    for( int y=0; y<h; y++ ) 
    {
    unsigned char *p = data + Bpr*y;

        for( int x=0; x<w; x++ ) 
        {
            if( color != (*((uint32_t *)p) & mask) )
                return( nil );
            p += spp;
        }
    }

    return( [raw_img colorAtX:0 y:0] );
}

(Some error checking removed for brevity - above code assumes 3 samples per pixel.)

The code that calls it is basically:

NSString *imageFile;
while( imageFile = [self getNextImageFile] )
{
NSImage *image = [[NSImage alloc] initWithContentsOfFile:imageFile];
    if( [image isValid] && [self getSolidColor:image] )
        [fileManager removeItemAtPath:imageFile error:nil];
}

The problem is that the application consumes an exraordinary amount of memory. When I run it in the profiler, it indicates that 90% of the memory used is being allocated by [NSImage TIFFRepresentation] (bolded above.)

i.e. The TIFFRepresentation data and NSBitmapImageRep are never freed even though they fall out of scope as soon as the function returns.

Why? And what should/can I do to force ARC to release those blocks?

With the 'old way' using non-ARC I would just put an autorelease pool inside the while loop for the images and that would take care of the problem. Is there such a concept with ARC?

Thanks.

(PS. NSZombies is NOT enabled.)

Kazuki Sakamoto
  • 13,929
  • 2
  • 34
  • 96
Kevin Franklin
  • 187
  • 1
  • 11
  • It might be relevant to point out that all of this is occurring in a background thread. When the thread exits, all of the memory is properly released, much like an autorelease pool would do. – Kevin Franklin May 05 '12 at 22:46

1 Answers1

4

Autorelease pools are conceptually still available with ARC, you just can't use the NSAutoreleasePool class anymore.

Use the new @autoreleasepool keyword instead:

@autoreleasepool {
   //Do stuff that you previously would have wrapped in an NSAutoreleasePool...
}
omz
  • 53,243
  • 5
  • 129
  • 141
  • Indeed!!! Just found that here as well http://stackoverflow.com/questions/9770213/arc-memory-leaks – Kevin Franklin May 05 '12 at 23:01
  • For clarity, it appears that the OSX frameworks are not using ARC (at least not fully) so the API call still requires an autorelease pool and wrapping the inside of the loop with @autoreleasepool solved the problem. – Kevin Franklin May 05 '12 at 23:04
  • 1
    @KevinFranklin: Whether the frameworks use ARC is irrelevant (by design); when you create a thread, you are responsible for putting an autorelease pool in place to catch any objects that get autoreleased on that thread. (I would be surprised if you weren't getting log messages in your Console about this.) – Peter Hosey May 05 '12 at 23:42
  • I *DO* have many threads and am not using autorelease pools around them and have *NOT* received any warnings about it, or any memory leaks. I simply assumed this was being handled for me now? – Kevin Franklin May 06 '12 at 00:39
  • @KevinFranklin: How are you (or are you) creating the threads? – Peter Hosey May 06 '12 at 02:21
  • @KevinFranklin: I've been testing this myself and I'm finding that Cocoa is now very inconsistent about giving the log message. `[[[NSObject alloc] init] autorelease]` (under MRC) causes it, but `[NSData dataWithBytes:length:]` (under MRC or ARC) does not. Spawning a thread, whether with simplified NSThread, OO NSThread, or `performSelectorInBackground:withObject:`, causes a log message about the NSThread object. I'd chalk the inconsistency up to a Cocoa or libobjc bug. But whether you get the log message or not, you still are obliged to wrap your thread body in a pool. – Peter Hosey May 06 '12 at 02:37
  • The main threads are created with [self performSelectorInBackground...], but most other threads are within an NSOperation. But this is phenomenal information that I had not gathered elsewhere and explains small memory leaks I had suspected in a different (IOS ARC) project but ignored for the lack of warnings. THANKS!!! – Kevin Franklin May 07 '12 at 05:41