I don’t normally post highly technical stuff to my blog, but here’s an exception that I hope will benefit the Mac and iOS (iPhone & iPad) developer community. So if you’re not part of that, feel free to skip it.
This article is a cumulative list of the most commonly-violated best practices for Objective-C coders that I’ve witnessed in my many years of experience with the language. I call them “commandments” because there are many good reasons to observe them and few, if any valid reasons not to. However, when I show these practices to other developers, they frequently raise one big objection…
This is almost never a legitimate objection. If you can show me any case where code written in conformance to any of the following policies has become the performance bottleneck, you have my blessing to optimize that code with an exception to the policy, as long as how you’re breaking the policy and why are clearly documented in the code.
These policies target code safety, clean memory management, understandability, and maintainability. Most of them are not performance enhancers. Nonetheless, writing code with performance in mind is never the way to go first. Always write clean, correct code and then optimize only when and where profiling indicates you need it. In a typical UI-driven application, the single biggest bottleneck is almost always the user, followed by network access, and then disk access. Nonetheless, premature optimization is rampant among programmers who are concerned that if they don’t use every optimization trick they’ve been taught, their application will be dog slow. This simply isn’t true.
Even if you understand how your code operates when you write it, that doesn’t mean others will understand it so completely before they attempt to modify it, and it doesn’t mean that you will understand it yourself when you revisit it months later. ALWAYS strive to make your code self-documenting by using verbose symbol names and add detailed comments in sections of code where meaning is not obvious despite clear and descriptive symbols. Any time you need to work around a bug or misfeature in an API you are using, isolate your workaround to a single method, explain your workaround and put a keyword like KLUDGE in your comments so you can easily find and fix these trouble spots later.
ALWAYS use the @private directive before any instance variable declarations, and NEVER access data members directly from outside the class.
ALWAYS create a @property for every data member and use “self.name” to access it throughout your class implementation. NEVER access your own data members directly.
ALWAYS use the “nonatomic” attribute on your properties, unless you are writing a thread-safe class and actually need access to be atomic, and then put a comment in the code that this was your intent.
Classes with properties that aren’t declared “nonatomic” may give the impression that they were designed with thread-safety in mind, when in fact they weren’t. Only drop the “nonatomic” qualifier from properties when you have actually designed the class for thread-safety.
NEVER allow your instance variable names to be confused with property names, or with data member names. ALWAYS end your instance variable names with an underscore. UNLESS you are subclassing a 3rd-party class which already has a data member of the same name, in which case pick another non-similar name or add another underscore and put a comment in as to why you did this.
Use “@synthesize name = name_;” in your implementation, instead of just “@synthesize name;”
NEVER redundantly add the data member to the class @interface yourself. Allow the @synthesize directive to implicitly add the data member.
Following this practice will yield many classes that explicitly declare no instance variables in the @interface section. When a class has no data members, omit the @private declaration, and even omit the opening and closing braces of the data member section.
You may still need to declare the underlying name of the variable if you need to access it directly, as when writing custom getters and setters:
@property(nonatomic, retain) NSObject* myObj;
@end
// …
@implementation Foo
@synthesize myObj = myObj_;
- (NSObject*)myObj
{
return myObj_; // No problem.
}
- (void)setMyObj:(NSObject*)myObj
{
// no problem
[myObj retain]; // retain the argument
[myObj_ release]; // release the instance variable
myObj_ = myObj; // do the assignment
}
@end
NEVER access a data member through an underscore-suffixed symbol UNLESS you are writing a setter or getter.
@property(nonatomic, retain) Bar* myObj;
@end
// …
@implementation Foo
@synthesize myObj = myObj_;
- (void)someMethod
{
myObj_ = [[Bar alloc] init];
}
@end
@property(nonatomic, retain) Bar* myObj;
@end
// …
@implementation Foo
@synthesize myObj = myObj_;
- (void)someMethod
{
self.myObj = [[[Bar alloc] init] autorelease];
}
@end
NEVER declare internal (private) methods or properties in the class header files. ALWAYS put all internal method and property declarations into a “class extension” in the implementation file.
@interface Foo : NSObject
@property(nonatomic) int myPublicProperty;
@property(nonatomic, retain) Bar* myPrivateProperty; // This can be accessed by anyone who includes the header
- (int)myPublicMethod;
- (int)myPrivateMethod; // So can this.
@end
@interface Foo : NSObject
// Only the public API can be accessed by including the header
@property(nonatomic) int myPublicProperty;
- (int)myPublicMethod;
@end
@interface Foo () // This is a "class extension" and everything declared in it is private, because it’s in the implementation file
@property(nonatomic, retain) Bar* myPrivateProperty;
- (int)myPrivateMethod;
@end
@implementation Foo
// …
@end
ALWAYS call -autorelease on any instance handed back to you from an init-constructor.
Autorelease pools establish a deallocation policy for the instance at the point of allocation. Failing to do this is likely to result in memory leaks if you forget to manually call -release on the instance. As code gets modified and moved around by other programmers, the -release call is prone to get moved, deleted, or just farther away from the allocation. This can be a particular problem in methods that have more than one exit point (i.e., return-statements.)
- (void)someMethod
{
Bar* bar = [[Bar alloc] init];
// … do a lot of stuff with bar…
// …
// …
// …
// …
// …
// …
[bar release]; // easy to forget about!
}
@end
- (void)someMethod
{
Bar* bar = [[[Bar alloc] init] autorelease];
// … do a lot of stuff with bar…
// …
// …
// …
// …
// …
// …
// nothing to forget about here!
}
@end
NEVER use more than one return-statement in a method, but only as the last statement of a method that has a non-void value-type.
In methods that have a non-void value-type, declare a variable for the value as the very first statement, and give it a sensible default value. Assign to it in code paths as necessary. Return it in the last statement. NEVER return it prematurely using a return-statement.
Premature return-statements increase the possibility that some sort of necessary resource deallocation will fail to execute.
- (Bar*)barWithInt:(int)n
{
// Allocate some resource here…
if(n == 0) {
// …and deallocate the resource here…
return [[Bar alloc] init] autorelease];
} else if(n == 1) {
// …and here…
return self.myBar;
}
// …and here.
return nil;
}
@end
- (Bar*)barWithInt:(int)n
{
Bar* result = nil;
// Allocate some resource here…
if(n == 0) {
result = [[Bar alloc] init] autorelease];
} else if(n == 1) {
result = self.myBar;
}
// …and deallocate the resource here, you’re done!
return result;
}
@end
Understand what NSAutoreleasePools are for, when they are created and destroyed for you, and when to create and destroy your own.
ALWAYS prefer class-level convenience constructors over init-constructors. All of the foundation framework container classes provide these.
ALWAYS offer class-level convenience constructors in the API of the classes you write.
Clients of your class can reap the benefits of being able to keep the previous commandment.
- (id)initWithBar:(int)bar baz:(int)baz;
+ (Foo*)fooWithBar:(int)bar baz:(int)baz;
@end
NEVER release something you didn’t init!
ALWAYS let your properties do retaining and releasing for you, if needed. NEVER call -retain or -release on objects yourself, UNLESS you are writing a setter for a property with the (retain) attribute, then be VERY CAREFUL.
-release may (or may not) cause an instance to become deallocated, but does not remove the variable that contains its reference from the current scope. This increases the possibility that someone will write code after the -release call that references the now-dangling pointer.
ALWAYS use “self.propertyName = nil;” to release an object, whether or not that object’s property uses the “retain” strategy.
This idiom can be used not matter what your memory management strategy is (which can change over time), and even works when assigning nil to a custom setter that does special resource deallocation behind the scenes (like invalidating an NSTimer.)
ALWAYS have a -dealloc method if your class keeps even one object pointer.
ALWAYS make sure that your dealloc method assigns nil to every self object property, even if your current policy doesn’t retain the object.
ALWAYS write a custom getter for a property where you have a custom setter, and vice versa.
Getters and setters need to have symmetrical behavior about memory management, thread safety, and any other side effects they create. You should not rely on a synthesized getter or setter having the proper symmetrical behavior to any custom getter or setter you write. Therefore, if you’re going to provide either the getter or setter yourself, you should provide both.
ALWAYS write a custom setter for a property if there’s something you must ALWAYS do when you release an object (such as invalidating a timer).
@synthesize myTimer;
- (void)dealloc
{
self.myTimer = nil; // Timer not invalidated, we could get called back if the timer fires after we’re dealloced!
[super dealloc];
}
@end
@synthesize myTimer = myTimer_;
- (NSTimer*)myTimer
{
return myTimer_;
}
- (void)setMyTimer:(NSTimer*)timer
{
[timer retain];
[myTimer_ invalidate];
[myTimer_ release];
myTimer = timer;
}
- (void)dealloc
{
self.myTimer = nil; // Timer guaranteed not to fire after we’re gone!
[super dealloc];
}
@end
When writing constructors, ALWAYS minimize or eliminate the amount of code that executes before [super init] is called.
In general, the call to the superclass’ constructor may fail, causing it to return nil. If that happens, then any initialization you do before the call to the super constructor is worthless or worse, has to be undone.
- (id)initWithBar:(Bar*)bar
{
[bar someMethod];
// other pre-initialization here
if(self = [super init]) {
// other initialization here
} else {
// oops! failed to initialize super class
// undo anything we did above
}
return self;
}
@end
- (id)init
{
if(self = [super init]) {
// minimal initialization here
}
return self;
}
// Other methods that put a Foo into a usable state
@end
When writing a UIViewController and not using a Nib file, ALWAYS create and setup your view hierarchy in -loadView, and never in -init. In your implementation of -loadView, ALWAYS call [super loadView] first. Your implementation of -loadView is the ONLY place you should ever assign to the view attribute.
NEVER call -loadView yourself! The view attribute of a UIViewController loads lazily when it’s accessed. It can also be automatically purged in a low-memory situation, so NEVER assume that a UIViewController’s view is going to live as long as the controller itself.
ALWAYS set up the views you need once, then show, hide, or move them as necessary. NEVER repeatedly destroy and recreate your view hierarchy every time something changes.
NEVER call -drawRect on a UIView yourself. Call -setNeedsDisplay.
ALWAYS avoid long compound operations in your code. Local variables are your friend.
NEVER use method names like -drawUI when you aren’t really drawing anything. ALWAYS prefer names like -setupUI or -resetUI, especially if there’s the possibility the method can be called more than once.
NEVER repeat yourself. Have a single, authoritative location for each piece of information or functionality, and deploy it throughout the application, even when it means touching others’ code. NEVER program via copy-and-paste.
Updated October 15, 2010
NEVER call -stringWithString UNLESS you:
NSStrings are immutable. You should never have to copy an NSString unless you want a mutable copy, or gurantee that an NSString pointer you want to save is not already an NSMutableString (and thus might have its value changed by some other code later.) In fact, attempts to copy NSStrings simply bump up the string’s retain count and return the original string:
When overriding a method in a superclass, ALWAYS call the superclass’ implementation, even if you know that super’s implementation does nothing, unless you have a very good reason to not call super’s implementation, in which case you must document your reason in the comments.
- (void)awakeFromNib
{
// do my setup
}
@end
- (void)awakeFromNib
{
[super awakeFromNib];
// do my setup
}
@end
Added October 15, 2010
In conditional statements, NEVER treat pointers or numerical values as booleans.
Booleans have two values: true and false (by convention in Objective-C we use YES and NO.) Pointers, on the other hand, have a value that is an object, or nil. Numerical values have values that are some number, or zero. While zero and nil evaluate to boolean false in a conditional context, this is a form of implicit type coercion that can make the meaning of your code more difficult to understand. So explicitly test for nil when dealing with pointers, 0 when dealing with integers, 0.0 when dealing with floating point values, etc.
Added October 15, 2010
NEVER use conditional statements to check for nil when you don’t have to. In particular, understand that in Objective-C, the act of calling any method on a nil object reference is a no-op that returns zero (or NO, or nil), and write your code accordingly.
As an aside, note that the order of operations in the setter above ensures that if “self.obj = self.obj;” (or its equivalent) is executed, and this property holds the only remaining reference to the object, we won’t accidentally cause the object’s retain count to drop to zero, which would cause an unintended deallocation and a dangling pointer.