Cocoa has several functions and methods that run a panel (usually as a sheet) asynchronously. These functions and methods take a target object, a selector, and a context pointer, and when they finish, they send a message to the target with that selector, passing that context pointer.
A common practice is to pass an object in the context pointer. You retain the object before you call the run-panel function or method, then release it in your callback. For example:
NSBeginAlertSheet(/*title*/ NSLocalizedStringFromTableInBundle(@"Update Available", nil, bundle, @""),
⋮
/*modalDelegate*/ self,
/*didEndSelector*/ NULL,
/*didDismissSelector*/ @selector(downloadSelector:returnCode:contextInfo:),
/*contextInfo*/ (void *)downloadURL,
…);
In this example, downloadURL is a local variable, and the object in it was retained above, or maybe not autoreleased in the first place. The downloadSelector:returnCode:contextInfo: method will cast the context pointer back to an object type, probably do something with it, then release it.
This has recently become a visible problem because of the Clang Static Analyzer. The analyzer doesn't know that NSBeginAlertSheet will retain the object, so it flags this call as a leak. This is the most-often-cited example of the checker's “false positives”: things that it flags as bugs but that aren't really bugs.
But is it really not a bug? Is it really appropriate to use an object as a context pointer?
Let's think about what we're doing here:
-
The panel doesn't know the context pointer is really an object, so it doesn't retain it. To compensate for this, we're retaining an object on behalf of another object (the panel). This alone should make you feel dirty.
-
The panel doesn't know the context pointer is really an object, so it won't release the object, either. This is particularly a problem if something releases the panel (quite possible in the case of, say, an NSAlert instance). Then it really is a leak.
-
If we get released (and, hopefully, take the panel with us or at least cancel it), then we leak the object, because we are only expecting to have to release it (or even to have access to it!) in the callback.
Passing objects through context pointers is an anti-pattern. I hope the Clang Static Analyzer developers never remove this warning, because this is not a false bug—it is a real bug that we should all fix in our programs.
Solutions
That object needs to go in an instance variable somewhere.
The lightweight solution is to create an instance variable on the object that is running the sheet. You store the object in the instance variable when you run the sheet (and leave the context pointer NULL), then retrieve the object from the instance variable in your callback. You'll still need to retain it when putting it in, and release it both in your callback and in dealloc—those aspects don't change.
I can hear you saying that this is a waste of an instance variable. What are they, rationed? There's nothing to waste; instance variables are cheap. And leaking objects is far more of a waste of memory than adding an instance variable.
But this isn't the only solution.
Another way is to create your own subclass of the panel class (NSOpenPanel, NSSavePanel, or NSAlert), and give the subclass a property you can use instead of the context pointer. You could make it abstract (making the property similar to NSMenuItem's representedObject, nothing more), or make it specific to your application. The former approach is simpler, whereas the latter gives you a base on which to build a more-customized alert if you later decide you want one. It's up to you.
The advantage of this solution is that, since you've formalized the instance variable as part of a class's API (and since the instance variable is no longer a bit player but the central part of a class), it'll be harder for you to forget to manage your ownership of it correctly. Memory problems solved.
One way or another, though, you need to put that object into an instance variable somewhere to avoid leaking memory, feeling dirty, and having the Clang Static Analyzer chew you out.
(And yes, all this applies with garbage collection as well. See the section “GC Gotchas” in episode 36 of Late Night Cocoa. However, I disagree with Andre Pang's recommendation in that episode of CFRetain and CFRelease; the right solution is not to dip into Core Foundation, but to avoid the context pointer altogether and use an instance variable instead.)