How I Manage Memory
I’ve noticed, in looking at other people’s Cocoa code over the years, that sometimes people still do weird things with retain, release, and autorelease — as if they’re not quite sure on the basics of memory management yet.
So I thought I’d talk about how I do things. There are three important points, then a practical explanation of one of them.
-
Overall memory use is a design issue. It’s not usually a case of over-using autorelease or something like that. (For instance: are all your objects in memory at all times, or do you use something like Core Data so you don’t have to do that?)
-
Memory management — the nuts and bolts of making sure you don’t leak or over-release — should be as simple as possible and done in the same way every time.
-
The exceptions to #2 should be done only as a result of profiling in Shark and Instruments.
Practical explanation of #2
I find not-leaking and not-over-releasing very, very easy. That’s because I have a simple system, and I do things the same way every time, except when profiling tells me I need to make an exception. (Which is rare.)
Here’s what I do:
I use properties, and I use the full form: self.something
and self.something = whatever
. I try to avoid custom accessor methods, just use the standard synthesized methods.
I don’t use the full form in init
and dealloc
, though, because it might trigger KVO or have other side effects.
So a made-up class might look like this:
@interface BSThing : NSObject { @private NSString *something; } - (id)initWithString:(NSString *)aString; @property (nonatomic, retain) NSString *something; @end @implementation BSThing @synthesize something; - (id)initWithString:(NSString *)aString { self = [super init]; if (self == nil) return nil; something = [aString retain]; return self; } - (void)dealloc { [something release]; [super dealloc]; } - (void)someRandomMethodThatDoesStuff { //Let's just change something self.something = @"Something else"; } @end
Make sense? Outside of init
and dealloc
, access is always via self.something
.
There are two other things I do:
Pretty much create everything as autoreleased
Seriously. I almost never write code like this:
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; //do something with dict [dict release];
It’s more code and it complicates things and it’s easy to make mistakes. I get confused. I don’t understand at-a-glance that the code is correct. I know there is advice to avoid autorelease
on iPhone — but you already know you can’t avoid it, and I’ve found that any drawbacks by use of autorelease
are over-shadowed by other issues.
So here’s what I always write:
NSMutableDictionary *dict = [NSMutableDictionary dictionary]; //do something with dict, completely un-worried about leaking it. //I can even return early.
Autorelease pools
One exception to the above is, of course, when you’re doing a bunch of allocations in a loop. Then I will use an inner autorelease
pool. I won’t do that crazy thing where you make it drain every 10 times or whatever — I’ll drain it in each pass. Simpler — and no reason to change that unless Shark or Instruments says to change it.
I’ve been doing it that way since reading Mike Ash’s article Autorelease is Fast. Though, obviously, it’s not as fast on iPhone, it’s still probably faster than you think it is.
Recap: my simple rules
-
Use properties, and use synthesized accessors as much as possible.
-
Always use the self.something form — no direct access to ivars, except in
init
anddealloc
, where only direct access is allowed. -
Create temporary objects, or objects that get returned by a method, as autoreleased.
-
Use autorelease pools as needed, but don’t try anything fancy.
-
Profile in Shark and Instruments, and make exceptions to the above only when it makes a real difference.
-
Consider that your overall memory use issues are probably design issues, not (usually) code issues.
Following these rules, writing Cocoa code is damn close to scripting.