Guillaume
Guillaume

Reputation: 4361

How to manage the autorelease pool of a NSRunLoop running in a secondary thread?

In Apple's MVCNetworking sample code, the NetworkManager class includes this method to maintain a run loop in a secondary thread dedicated to network activity (in order to run NSURLConnection asynchronously):

- (void)networkRunLoopThreadEntry
{
    while(YES) {
        NSAutoreleasePool *pool;
        pool = [[NSAutorelease alloc] init];
        [[NSRunLoop currentRunLoop] run];
        [pool drain];
    }
}

Since the run method exits immediately if there is no source attached to the run loop, this looks like an infinite while loop which is going uselessly to consume CPU resources if there is currently no NSURLConnection attached to the run loop.

On the other hand, to keep the run loop active, some suggests to schedule an empty port in the run loop:

 - (void)networkRunLoopThreadEntry
 {
     NSAutoreleasePool *pool = [[NSAutorelease alloc] init];
     NSPort *port = [NSPort port];
     [[NSRunLoop currentRunLoop] addPort:port forMode:NSRunLoopCommonModes];
     [NSRunLoop run];
     [pool drain];
 }

However, in this case, my worry is that the run method will never exit, which means the pool will never get drained, which means all objects allocated and autoreleased in the secondary thread will leak.

What is the way to go then?

(For the context, as many others, I'm trying to encapsulate an asynchronous NSURLConnection inside a NSOperation, which means it can be triggered outside of the main thread. Also, the MVCNetworking sample code, as well as the WWDC 2010 sessions Network Apps for iPhone OS, seem to suggest it is a good idea to have a unique secondary thread dedicated to network transfers to prevent latency on the main thread.)

Upvotes: 4

Views: 2570

Answers (1)

rob mayoff
rob mayoff

Reputation: 385650

You can create a CFRunLoopObserver for the kCFRunLoopBeforeWaiting activity and add it to the run loop. In the observer's callout, release the old pool and create a new one. Untested example:

static void resetPoolCallout(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    NSAutoreleasePool **poolPointer = (NSAutoreleasePool **)info;
    [*poolPointer release];
    *poolPointer = [[NSAutoreleasePool alloc] init];
}

- (void)networkRunLoopThreadEntry {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSPort *port = [NSPort port];
    [[NSRunLoop currentRunLoop] addPort:port forMode:NSRunLoopCommonModes];

    CFRunLoopObserverContext observerContext = {
        .version = 0,
        .info = (void*)&pool,
        .retain = NULL,
        .release = NULL,
        .copyDescription = NULL
    };
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting,
        true, 0, resetPoolCallout, &observerContext);
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);

    [[NSRunLoop currentRunLoop] run];

    CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
    CFRelease(observer);

    [pool release];
}

Upvotes: 6

Related Questions