3

I am running NSInvocationOperation-type operations in an NSOperationQueue and was wondering if it is safe to autorelease objects - That is, if it is guaranteed that the thread started for each operation has its own autorelease pool.

I didn't find any documentation autorelease pools for operations - Reading Apple's docs actually suggests I do need to define my own autorelease pool.

However: 1) I can't see any leaks in instruments, at least not more than when I allocate my own autorelease pool in the operation.

2) looking in the debugger I can see this stack trace:

#0  0x00fc3e82 in -[NSObject(NSObject) release] ()
#1  0x00faaa6c in CFRelease ()
#2  0x00fbf804 in __CFBasicHashDrain ()
#3  0x00faabcb in _CFRelease ()
#4  0x00fcfb8d in _CFAutoreleasePoolPop ()
#5  0x000edd0d in -[__NSOperationInternal start] ()
#6  0x000ed826 in ____startOperations_block_invoke_2 ()
#7  0x94358024 in _dispatch_call_block_and_release ()
#8  0x9434a2f2 in _dispatch_worker_thread2 ()
#9  0x94349d81 in _pthread_wqthread ()
#10 0x94349bc6 in start_wqthread ()

So it looks as though there is a CFAutoreleasePool - Is it safe to assume this object will call release on all my autoreleased objects when the operation is complete?

Danra
  • 9,546
  • 5
  • 59
  • 117

1 Answers1

4

I’ve written a small program to test whether NSInvocationOperation would create an autorelease pool for the operation:

#import <Foundation/Foundation.h>

@interface MyClass : NSObject
@end

@implementation MyClass
- (void)performSomeTask:(id)data
{
    NSString *s = [[[NSString alloc] initWithFormat:@"hey %@", data]
        autorelease];
    if ([[NSThread currentThread] isMainThread])
        NSLog(@"performSomeTask on the main thread!");
    else
        NSLog(@"performSomeTask NOT on the main thread!");

    NSLog(@"-- %@", s);
}
@end

int main(int argc, char *argv[]) {
  MyClass *c = [MyClass new];

  if (argc == 2 && strcmp(argv[1], "nop") == 0)
      [c performSomeTask:@"ho"];
  else {
      NSInvocationOperation *op = [[NSInvocationOperation alloc]
          initWithTarget:c
                selector:@selector(performSomeTask:)
                  object:@"howdy"];
      NSOperationQueue *queue  = [[NSOperationQueue alloc] init];
      [queue addOperation:op];
      [op waitUntilFinished];

      [op release];
      [queue release];
  }

  [c release];

  return 0;
}

It works as follows: if "nop" is passed on the command-line, it will execute -performSomeTask: directly, on the main thread, with no autorelease pool in place. The resulting output is:

$ ./c nop
*** __NSAutoreleaseNoPool(): Object 0x10010cca0 of class NSCFString autoreleased with no pool in place - just leaking
performSomeTask on the main thread!
-- hey ho

The autoreleased string in -performSomeTask: causes a leak.

Running the program without passing "nop" will execute -performSomeTask: via an NSInvocationOperation on a different thread. The resulting output is:

$ ./c
*** __NSAutoreleaseNoPool(): Object 0x100105ec0 of class NSInvocation autoreleased with no pool in place - just leaking
*** __NSAutoreleaseNoPool(): Object 0x100111300 of class NSCFSet autoreleased with no pool in place - just leaking
*** __NSAutoreleaseNoPool(): Object 0x100111b60 of class NSCFSet autoreleased with no pool in place - just leaking
*** __NSAutoreleaseNoPool(): Object 0x100105660 of class NSCFSet autoreleased with no pool in place - just leaking
performSomeTask NOT on the main thread!
-- hey howdy

As we can see, there are instances of NSInvocation and NSSet that are leaking but the autoreleased string in -performSomeTask: isn’t leaking, hence an autorelease pool was created for that invocation operation.

I think it’s safe to assume that NSInvocationOperation (and probally all NSOperation subclasses in Apple’s frameworks) create their own autorelease pools, just like the Concurrency Programming Guide suggests for custom NSOperation subclasses.

Danra
  • 9,546
  • 5
  • 59
  • 117
  • Did you test this on iOS 3.x? This might be a GCD implementation detail as described here: http://stackoverflow.com/questions/4141123/do-you-need-to-create-an-nsautoreleasepool-within-a-block-in-gcd – Danra Jan 18 '11 at 15:01
  • @Danra AFAICT, NSOperationQueue doesn’t use GCD. Also, if you change the code above to run synchronously via `[op start]`, there’ll be no message showing that the string is leaking, which means that `NSInvocationOperation` creates an autorelease pool for the operation. –  Jan 18 '11 at 15:12
  • @Danra I’ve only tested this behaviour on Mac OS 10.6.6. –  Jan 18 '11 at 15:12
  • I’ve filed a radar asking for the documentation to explicitly state that the Foundation `NSOperation` subclasses create their own autorelease pools. –  Jan 18 '11 at 15:15
  • I'm assuming GCD is used because of the call to _dispatch_call_block_and_release I saw in the stack trace. Thanks for your radar, please update this thread if you get an official answer. – Danra Jan 18 '11 at 15:18
  • @Danra Yes, good point, but I’d assume that’s because of `NSOperationQueue` and not `NSInvocationOperation` per se. As for official answers, don’t hold your breath — it could take months. ;-) –  Jan 18 '11 at 15:26
  • Quinn the Eskimo answered my question in the Apple Developer Forums and confirmed that autorelease pools were also allocated for operations before GCD was introduced - See https://devforums.apple.com/message/365667 – Danra Jan 19 '11 at 13:21
  • @Danra Good to know. Let’s hope this makes into the documentation, too. –  Jan 19 '11 at 13:32