If you have a multithreaded iPhone/iPad/Cocoa application, you are probably aware that for each thread you create you need to set up an auto-release pool for that thread. If you don’t do this then you’ll get some nice messages in your debugger log informing you that your app is leaking memory (for shame!). Personally I think that any boilerplate code that must be added to each thread should be handled automatically by the SDK/runtime environment, but that’s completely beside the point here. The point here is that the standard example given for this boilerplate code is generally something along the lines of:
- (void) myThreadEntryPoint {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; //set up a pool
// [do work here]
[pool drain];
}
And this is all well and good for simple use-cases, but often a developer wants to have a thread that runs forever (or for the lifetime of the application), in which case the ‘[do work here]‘ section might look like:
while (! [self shouldTerminate]) {
//[do some stuff]
[NSThread sleepForTimeInterval:10.0s]; //sleep for a bit
}
If we insert this code into the standard boilerplate example, we get the following:
- (void) myThreadEntryPoint {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; //set up a pool
while (! [self shouldTerminate]) {
//[do some stuff]
[NSThread sleepForTimeInterval:10.0s]; //sleep for a bit
}
[pool drain];
}
And now we have a problem, one that’s particularly easy for new developers to create. Technically this code is following the standard example, but it is also creating a slow memory leak, assuming that any amount of non-trivial work is being performed in the ‘[do some stuff]‘ section. The problem is that the auto-release pool is never drained until the thread is ready to terminate, meaning that all the objects that are in the pool do not get released. They simply accumulate in memory. If you write code like what’s shown above, a crash is inevitable; it’s only a question of when.
In an environment as memory-constrained as an iPhone, anything accumulating in memory is a Very Bad Thing™. Doubly so in this case because the issue will not be detected in debugging tools like Leaks or Allocations. You will not get any console messages nagging you about memory leaks. You have an auto-release pool in place, after all, and you’re releasing it properly, so how is the compiler or any other tool to know that there’s an issue (in fact, in order to detect that there is an issue in the above code the compiler would have to be able to solve the halting problem)?
If you code like this, your application will just slowly consume more and more memory until it eventually crashes. And you can’t even count on getting a reliable stack-trace when it does crash, because the allocation that finally brings the thing crashing down might be nowhere near the code associated with the actual leak.
Luckily, this error is simple to avoid. Just change the code so that it’s like this:
- (void)myThreadEntryPoint {
while (![self shouldTerminate]) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//[do work here]
[pool drain];
[NSThread sleepForTimeInterval:10.0s]; //sleep for a bit
}
}
And problem solved. The auto-release pool is released and reset on each iteration of the loop, as soon as we are done doing our actual work. Objects are released, memory is freed, everyone is happy, and on the next loop iteration the process starts over again. It’s a very simple fix, but the issue that it addresses is easy to overlook, and difficult to track down once overlooked.
Note that this is mentioned in Apple’s official documentation on the subject, which states:
If your application or thread is long-lived and potentially generates a lot of autoreleased objects, you should periodically drain and create autorelease pools (like the Application Kit does on the main thread); otherwise, autoreleased objects accumulate and your memory footprint grows. If, however, your detached thread does not make Cocoa calls, you do not need to create an autorelease pool.
So if you missed it in the official documentation before, now you know; and hopefully you also know why it’s important to pay attention to this little piece of advice. If you don’t want your application to crash and die randomly, that is.
As if!