The Code Commandments: Best Practices for OCCoding

Preface

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.

Introduction

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…

The Big Objection: Won’t That Hurt Performance?

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.

WHY?

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.

Important General Advice

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.

Things This Article Doesn’t Cover (Yet)

  • Key Value Coding (KVC) and Key Value Observing (KVO). You should use them.

Added April 26, 2012

THOU SHALT…

ALWAYS use Automatic Reference Counting (ARC). This article has been revised to remove references to pre-ARC code style. All new code should be written using ARC, and all legacy code should be updated to use ARC (or kept in a very tight box with a pretty API covering it up.)

THOU SHALT…

ALWAYS use the @private directive before any instance variable declarations, andNEVER access instance variables directly from outside the class.

WHY?

  • Preserves information hiding (encapsulation)

  • Limits to methods in the class implementation cases where instance variables are accessed directly.

  • The default privacy for instance variables is @protected , which means subclasses can access these instance variables freely. But there has to be a very good reason to allow subclasses to do this— everything that a superclass exposes to the outside world becomes part of its contract, and the ability to change the internal representation of a class’s data without changing its API or contract is an important benefit of object-oriented encapsulation. By keeping your instance variables private, you make it clear that they are implementation details and not part of your class’s API.

BAD

  1. @interface  Foo : NSObject {

  2.         int a;

  3.         NSObject* b;

  4.         // …

  5. }

  6. // method & property declarations…

  7. @end

BETTER

  1. @interface  Foo : NSObject {

  2.         @private

  3.         int a;

  4.         NSObject* b;

  5.         // …

  6. }

  7. // method & property declarations…

  8. @end

THOU SHALT…

ALWAYS create a @property for every data member and use “self.name” to access it throughout your class implementation. NEVER access your own instance variables directly.

WHY?

  • Properties enforce access restrictions (such as readonly)

  • Properties enforce memory management policy (strong, weak)

  • Properties provide the opportunity to transparently implement custom setters and getters.

  • Properties with custom setters or getters can be used to enforce a thread-safety strategy.

  • Having a single way to access instance variables increases code readability.

BAD

  1. @interface  Foo : NSObject {

  2.         @private

  3.         NSObject* myObj;

  4. }

  5. @end

  6.  

  7. @implementation Foo

  8. - (void)bar {

  9.         myObj = nil;

  10. }

  11. @end

BETTER

  1. @interface  Foo : NSObject {

  2.         @private

  3.         NSObject* myObj;

  4. }

  5.  

  6. @property(strong, nonatomic) NSObject* myObj;

  7.  

  8. @end

  9.  

  10. @implementation Foo

  11. - (void)bar {

  12.         self.myObj = nil;

  13. }

  14. @end

THOU SHALT…

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.

WHY?

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.

BAD

  1. @interface  Foo : NSObject

  2.  

  3. @property(strong) NSObject* myObj;

  4.  

  5. @end

BETTER

  1. @interface  Foo : NSObject

  2.  

  3. @property(strong, nonatomic) NSObject* myObj;

  4.  

  5. @end

BETTER

  1. // This class and all it’s properties are thread-safe.

  2. @interface  Foo : NSObject

  3.  

  4. @property(strong) NSObject* myObj;

  5.  

  6. @end

THOU SHALT…

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 andput a comment in as to why you did this.

Use “@synthesize name = name_;” in your implementation, instead of just “@synthesize name;”

WHY?

  • Even within the class implementation you have to have a very good reason to access a data member directly, instead preferring property accessors.

  • Apple uses “_name” for their private instance variables, and by using “name_” for yours, you avoid naming conflicts.

  • Apple has begun to use the “name_” convention in code examples and templates that demonstrate developer-level code.

BAD

  1. @interface  Foo : NSObject

  2.  

  3. @property(strong, nonatomic) NSObject* myObj;

  4.  

  5. @end

  6.  

  7. // …

  8.  

  9. @implementation Foo

  10.  

  11. @synthesize myObj;

  12.  

  13. @end

BETTER

  1. @interface  Foo : NSObject

  2.  

  3. @property(strong, nonatomic) NSObject* myObj;

  4.  

  5. @end

  6.  

  7. // …

  8.  

  9. @implementation Foo

  10.  

  11. @synthesize myObj = myObj_;

  12.  

  13. @end

THOU SHALT…

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 instance variables, omit the @private declaration, and even omit the opening and closing braces of the data member section.

WHY?

  • Reduces redundancy.

  • Simplifies class headers.

  • Avoids the need for forward class declarations in public headers merely so you can declare members of classes that are only actually used in the implementation.

BAD

  1. @interface  Foo : NSObject {

  2.         @private

  3.         NSObject* myObj_;

  4. }

  5.  

  6. @property(strong, nonatomic) NSObject* myObj;

  7.  

  8. @end

  9.  

  10. // …

  11.  

  12. @implementation Foo

  13.  

  14. @synthesize myObj = myObj_;

  15.  

  16. @end

BETTER

  1. @interface  Foo : NSObject

  2.  

  3. @property(strong, nonatomic) NSObject* myObj;

  4.  

  5. @end

  6.  

  7. // …

  8.  

  9. @implementation Foo

  10.  

  11. @synthesize myObj = myObj_;

  12.  

  13. @end

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:

WON’T WORK

  1. @interface  Foo : NSObject

  2.  

  3. @property(strong, nonatomic) NSObject* myObj;

  4.  

  5. @end

  6.  

  7. // …

  8.  

  9. @implementation Foo

  10.  

  11. @synthesize myObj;

  12.  

  13. - (NSObject*)myObj

  14. {

  15.         return self.myObj; // recursive call to this getter!

  16. }

  17.  

  18. - (void)setMyObj:(NSObject*)myObj

  19. {

  20.         self.myObj = myObj; // recursive call to this setter!

  21. }

  22.  

  23. @end

WILL WORK

  1. @interface  Foo : NSObject

  2.  

  3. @property(strong, nonatomic) NSObject* myObj;

  4.  

  5. @end

  6.  

  7. // …

  8.  

  9. @implementation Foo

  10.  

  11. @synthesize myObj = myObj_;

  12.  

  13. - (NSObject*)myObj

  14. {

  15.         return myObj_; // No problem.

  16. }

  17.  

  18. - (void)setMyObj:(NSObject*)myObj

  19. {

  20.         // no problem

  21.         myObj_ = myObj; // do the assignment (ARC handles any necessary retaining and releasing)

  22. }

  23.  

  24. @end

THOU SHALT…

NEVER access a data member through an underscore-suffixed symbol UNLESS you are writing a setter or getter.

BAD

  1. @interface  Foo : NSObject

  2.  

  3. @property(strong, nonatomic) Bar* myObj;

  4.  

  5. @end

  6.  

  7. // …

  8.  

  9. @implementation Foo

  10.  

  11. @synthesize myObj = myObj_;

  12.  

  13. - (void)someMethod

  14. {

  15.         myObj_ = [[Bar alloc] init];

  16. }

  17.  

  18. @end

BETTER

  1. @interface  Foo : NSObject

  2.  

  3. @property(strong, nonatomic) Bar* myObj;

  4.  

  5. @end

  6.  

  7. // …

  8.  

  9. @implementation Foo

  10.  

  11. @synthesize myObj = myObj_;

  12.  

  13. - (void)someMethod

  14. {

  15.         self.myObj = [[Bar alloc] init];

  16. }

  17.  

  18. @end

THOU SHALT…

NEVER declare internal (private) methods or properties in the class header files. ALWAYSput all internal method and property declarations into a “class extension” in the implementation file.

BAD

  1. //

  2. // Foo.h

  3. //

  4.  

  5. @interface  Foo : NSObject

  6.  

  7. @property(nonatomic) int myPublicProperty;

  8. @property(strong, nonatomic) Bar* myPrivateProperty; // This can be accessed by anyone who includes the header

  9.  

  10. - (int)myPublicMethod;

  11. - (int)myPrivateMethod; // So can this.

  12.  

  13. @end

BETTER

  1. //

  2. // Foo.h

  3. //

  4.  

  5. @interface  Foo : NSObject

  6.  

  7. // Only the public API can be accessed by including the header

  8.  

  9. @property(nonatomic) int myPublicProperty;

  10.  

  11. - (int)myPublicMethod;

  12.  

  13. @end

  1. //

  2. // Foo.m

  3. //

  4.  

  5. @interface  Foo () // This is a "class extension" and everything declared in it is private, because it’s in the implementation file

  6.  

  7. @property(strong, nonatomic) Bar* myPrivateProperty;

  8.  

  9. - (int)myPrivateMethod;

  10.  

  11. @end

  12.  

  13. @implementation Foo

  14. // …

  15. @end

THOU SHALT…

NEVER use more than one return-statement in a method, and only then 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.

WHY?

Premature return-statements increase the possibility that some sort of necessary resource deallocation will fail to execute.

BAD

  1. @implementation Foo

  2.  

  3. - (Bar*)barWithInt:(int)n

  4. {

  5.         // Allocate some resource here…

  6.  

  7.         if(== 0) {

  8.                 // …and you have to deallocate the resource here…

  9.                 return [[Bar alloc] init];

  10.         } else if(== 1) {

  11.                 // …and here…

  12.                 return self.myBar;

  13.         }

  14.  

  15.         // …and here.

  16.         return nil;

  17. }

  18.  

  19. @end

BETTER

  1. @implementation Foo

  2.  

  3. - (Bar*)barWithInt:(int)n

  4. {

  5.         Bar* result = nil;

  6.  

  7.         // Allocate some resource here…

  8.  

  9.         if(== 0) {

  10.                 result = [[Bar alloc] init];

  11.         } else if(== 1) {

  12.                 result = self.myBar;

  13.         }

  14.  

  15.         // …and deallocate the resource here, you’re done!

  16.  

  17.         return result;

  18. }

  19.  

  20. @end

THOU SHALT…

Understand what autorelease pools are for, when they are created and destroyed for you, and when to create and destroy your own.

  • Automatically by NSRunLoop on each pass

  • Automatically by NSOperation

  • By you, at the start and end of threads

  • By you, whenever you must create and release a large number of objects before you’ll cede control back to the run loop.

Under ARC, you create autorelease pools with the @autoreleasepool { … } construct.

THOU SHALT…

ALWAYS prefer class-level convenience constructors over init-constructors. All of the foundation framework container classes provide these.

BAD

  1. NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];

BETTER

  1. NSMutableDictionary* dict = [NSMutableDictionary dictionary];

THOU SHALT…

ALWAYS offer class-level convenience constructors in the API of the classes you write.

WHY?

Clients of your class can reap the benefits of being able to keep the previous commandment.

BAD

  1. @interface  Foo : NSObject

  2.  

  3. - (id)initWithBar:(int)bar baz:(int)baz;

  4.  

  5. @end

BETTER

  1. @interface  Foo : NSObject

  2.  

  3. - (id)initWithBar:(int)bar baz:(int)baz;

  4.  

  5. + (Foo*)fooWithBar:(int)bar baz:(int)baz;

  6.  

  7. @end

THOU SHALT…

ALWAYS understand when object ownership transfers happen. ARC handles a lot of this for you, but you should still be generally knowledgable of what is going on behind the scenes.

Common ways of taking posession of an object:

  • New objects are owned by you when you call +alloc on a class.

  • New objects are owned by you when you call -copy or -mutableCopy on an instance.

  • You become an owner of an existing object when you assign it to a property with the (retain) or (strong) attribute.

Common ways of releasing ownership an object:

  • Assign another object (or nil) to a property with the (strong) attribute.

  • Let an owning local variable go out of scope.

  • Another object holding a reference to the object is destroyed.

THOU SHALT…

ALWAYS understand the memory management policies of your properties and instance variables, particularly when writing custom getters and setters. Make sure your instance variables have the correct storage policies declared on them in order to let ARC do it’s job. The easiest way to do this is simply to use @synthesize and then override the generated setter and/or getter to access the underlying instance variable, which will have the same storage policy as that declared by the property.

  1. @interface  bar

  2.  

  3. @property (strong, nonatomic) id foo;

  4.  

  5. @end

  6.  

  7.  

  8. @implementation bar

  9.  

  10. @synthesize foo = foo_;

  11.  

  12. - (id)foo

  13. {

  14.         return foo_;

  15. }

  16.  

  17. - (void)setFoo:(id)foo;

  18. {

  19.         foo_ = foo;  // Retained/released automatically by ARC because of (strong) attribute on @property above

  20.         [self syncToNewFoo];  // The reason for our custom setter

  21. }

  22.  

  23. @end

THOU SHALT…

NEVER have a -dealloc method if your class unless it must release special resources (close files, release memory acquired with malloc(), invalidate timers, etc.) ARC should be allowed to take care of everything else.

THOU SHALT…

ALWAYS write a custom getter for a property where you have a custom setter, and vice versa.

WHY?

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.

THOU SHALT…

ALWAYS write a custom setter for a property, and assign that property to nil in your -dealloc method, if there’s something you must ALWAYS do when you release an object (such as invalidating a timer).

BAD

  1. @implementation Foo

  2.  

  3. @synthesize myTimer;

  4.  

  5. - (void)dealloc

  6. {

  7.         self.myTimer = nil// Timer not invalidated, we could get called back if the timer fires after we’re dealloced!

  8. }

  9.  

  10. @end

BETTER

  1. @implementation Foo

  2.  

  3. @synthesize myTimer = myTimer_;

  4.  

  5. - (NSTimer*)myTimer

  6. {

  7.         return myTimer_;

  8. }

  9.  

  10. - (void)setMyTimer:(NSTimer*)myTimer

  11. {

  12.         [myTimer_ invalidate];

  13.         myTimer_ = myTimer;

  14. }

  15.  

  16. - (void)dealloc

  17. {

  18.         self.myTimer = nil// Timer guaranteed not to fire after we’re gone! Still necessary under ARC.

  19. }

  20.  

  21. @end

WHY?

ARC takes care of releasing your (strong) or (retain) instance variables at -dealloc time, but it does it directly, rather than by calling your custom setters. So if your custom setters have other side effects (like invaliding timers) you must still make sure to invoke them yourself.

THOU SHALT…

When writing constructors, ALWAYS minimize or eliminate the amount of code that executes before [super init] is called.

WHY?

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.

BAD

  1. @implementation Foo

  2.  

  3. - (id)initWithBar:(Bar*)bar

  4. {

  5.         [bar someMethod];

  6.         // other pre-initialization here

  7.        

  8.         if(self = [super init]) {

  9.                 // other initialization here

  10.         } else {

  11.         // oops! failed to initialize super class

  12.         // undo anything we did above

  13. }

  14.  

  15.         return self;

  16. }

  17.  

  18. @end

BETTER

  1. @implementation Foo

  2.  

  3. - (id)init

  4. {

  5.         if(self = [super init]) {

  6.                 // minimal initialization here

  7.         }

  8.  

  9.         return self;

  10. }

  11.  

  12. // Other methods that put a Foo into a usable state

  13.  

  14. @end

THOU SHALT…

When writing a UIViewController and not using a nib file, ALWAYS create and setup your view hierarchy in -loadView, and never in -init. Your implementation of -loadView is the ONLY place you should ever assign to the view attribute.

THOU SHALT…

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 NEVERassume that a UIViewController’s view is going to live as long as the controller itself.

THOU SHALT…

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.

THOU SHALT…

NEVER call -drawRect on a UIView yourself. Call -setNeedsDisplay.

THOU SHALT…

ALWAYS avoid long compound operations in your code. Local variables are your friend.

BAD

  1. NSMutableDictionary* listDict = [[NSMutableDictionary alloc]initWithDictionary:[[NSUserDefaults standardUserDefaults]objectForKey:@"foo"]];

BETTER

  1. NSUserDefaults* defaults = [NSUserDefaultsstandardUserDefaults];

  2. NSDictionary* dataModelDict = [defaults objectForKey:@"foo"];

  3. NSMutableDictionary* listDict = [NSMutableDictionarydictionaryWithDictionary:dataModelDict];

WHY?

  • Self-documenting

  • Easier to understand

  • Easier to step through in debugger

THOU SHALT…

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.

THOU SHALT…

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

THOU SHALT…

NEVER call -stringWithString UNLESS you:

  • Are converting an NSString to an NSMutableString.

  • Are converting an NSMutableString to an NSString.

  • Need to guarantee that an NSString* you’ve been handed is really immutable

  • Really need a copy of an NSMutableString because you plan to modify them separately.

WHY?

NSStrings are immutable. You should never have to copy an NSString unless you want a mutable copy, or guarantee 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.
[/code]

Added April 26, 2012

THOU SHALT...

ALWAYS use the (copy) storage class for properties that receive objects (like NSStrings or NSArrays) that have mutable subclasses.

WHY?

The (copy) storage class of properties (as well as the -copy method) always make IMMUTABLE copies of these objects. So you can rely on the immutability of a copy, while you cannot rely on the immutability of the original. This is also a promise to the caller that you will not mutate objects that are passed to you this way that happen to be only incidentally mutable.

THOU SHALT...

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.

WHY?

  • Super's implementation might not ALWAYS do nothing in the future.

  • Other classes may eventually interpose themselves between your class and the superclass, with non-empty implementations.

  • Makes your code more self-documenting in that it is not possible to call a super implementation unless the method is an override.

BAD

  1. @implementation Foo

  2.  

  3. - (void)awakeFromNib

  4. {

  5.         // do my setup

  6. }

  7.  

  8. @end

BETTER

  1. @implementation Foo

  2.  

  3. - (void)awakeFromNib

  4. {

  5.         [super awakeFromNib];

  6.  

  7.         // do my setup

  8. }

  9.  

  10. @end

Added October 15, 2010

THOU SHALT...

In conditional statements, NEVER treat pointers or numerical values as booleans.

WHY?

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.

BAD

  1. - (void)fooWithBar:(Bar*)bar baz:(BOOL)baz quux:(float)quux

  2. {

  3.         if(bar && baz && quux) {

  4.                 // do something interesting

  5.         }

  6. }

BETTER

  1. - (void)fooWithBar:(Bar*)bar baz:(BOOL)baz quux:(float)quux

  2. {

  3.         if(bar != nil && baz && quux != 0.0) {

  4.                 // do something interesting

  5.         }

  6. }

Added October 15, 2010

THOU SHALT...

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.

BAD

  1. - (void)setObj:(NSObject*)obj

  2. {

  3.         if(obj_ != nil) {

  4.                 [obj_ doCleanup];

  5.         }

  6.         if(obj != nil) {

  7.                 [obj doSetup];

  8.         }

  9.         obj_ = obj;

  10. }

BETTER

  1. - (void)setObj:(NSObject*)obj

  2. {

  3.         [obj_ doCleanup]// Does nothing if obj_ == nil

  4.         [obj doSetup]// Does nothing if obj == nil

  5.         obj_ = obj;

  6. }


你可能感兴趣的:(The Code Commandments: Best Practices for OCCoding)