0

So I have some code running in an NSThread that creates, in a loop, a whole bunch of NSImages. A small section of each of the images is drawn into another NSImage, and then the thread exits.

So, something like

NSImage *outputImage = [[NSImage alloc] initWithSize:size];
[outputImage lockFocus];
while(1000 times)
    NSImage* image = [[NSImage alloc] initWithSize:size];
    ... image is processed ...
    [image drawInRect: ... fromRect: ... ]
[outputImage unlockFocus];

Once this is complete, the thread uses performSelectorOnMainThread to send the created NSImage back to the main thread to have it placed in a view.

This all works fine, and the final image is exactly as I expect it to be. However, during the loop the memory usage of the application rises linearly - as though each NSImage isn't released until some later time. My theory is that the drawInRect calls are being pipelined somewhere and not actually executed until later. Is this correct? And if so how do I prevent it? My application will crash if I make my loop counter too large at the moment, and I'd like to avoid that.

I've tried moving the locking and unlocking of focus into the loop, but this made no difference.

I've confirmed that if I take out only the drawInRect call, the memory usage is (more or less) flat for the lifetime of the thread. It's only when I put the call in that the memory climbs.

I'm (obviously?) using ARC in this project, and it's running in OSX10.9.

Dave Branton
  • 494
  • 3
  • 11

1 Answers1

2

It sounds like your NSImages are being added to the autorelease pool at some point. They won’t normally be flushed until the pool is drained.

In instances like this you want to make your own autorelease pool:

NSImage *outputImage = [[NSImage alloc] initWithSize:size];
[outputImage lockFocus];
while(1000 times) {
    @autoreleasepool {
        NSImage* image = [[NSImage alloc] initWithSize:size];
        ... image is processed ...
        [image drawInRect: ... fromRect: ... ]
    }
}
[outputImage unlockFocus];

This creates a sub-pool that will get drained on every pass, so you won’t have a huge build-up of images.

Wil Shipley
  • 9,343
  • 35
  • 59
  • Thank's very much - that's probably it. I'm interested though, I had assumed that automatic reference counting operated alot like using boost::shared_pointers everywhere (I'm from a C++ background). So that, once something fell out of scope or was assigned null, it would be freed. I assume from your comment that this is not the case with ARC, and I have to explicitly declare special blocks to make this happen. – Dave Branton Jan 22 '14 at 00:53
  • I believe there are instances where you can avoid autoreleasing entirely, I’m not sure what in your code triggered it in this case, if it is in fact what’s hurting you. – Wil Shipley Jan 22 '14 at 09:50
  • I tried this fix out last night, and it erases the issue completely. It's certainly interesting that not calling drawInRect also prevents the issue - although it also prevents the code from doing anything of course. I'll just have to remember to use @autorelease when it looks like I might need it. Thanks for your help. – Dave Branton Jan 22 '14 at 20:49