Thotz/Thotz.html
 

Robust Cocoa Coding

After 35 years of coding, some patterns emerge.


Recognizing these patterns and devising tactics to deal with them allow me to normally write hundred-to-thousand line chunks of code and have them Just Work.


Patterns:


  1. On any given day, you can be an idiot.


  2. – So code (and comment) for your inner idiot.


  3. Clarity beats efficiency, cleverness or brevity.


  4. Remember, it might be you (or your inner idiot) who has to decipher this code in a few years.


  5. Code for your own convenience.    


  6. Time spent making coding simpler is productive. Cater to your Inner Idiot.


  7. The less you need to know, the better.


  8. A given method should deal with a single, clear issue -- juggling too many "what-ifs" is your Inner Idiot’s invitation to the debugger. Debugging time is wasted time.



Applying these to Cocoa coding yields the following tactics:


  1. (Note, this was written before ObjC 2.0, but it all still applies if you are writing backwards-compatible or iPhone code. Or perhaps regardless.)


1. Always use accessors, and prefix instance variables with "_" to catch your Inner Idiot bypassing the accessor. Use an accessor generator to streamline this process. There is one built into XCode (an AppleScript) and there are others available. There is no Apple advice against using "_" as a prefix on instance variables, just on method names.


2. Use Class methods instead of globals and constants.


Example:


  1. Instead of:


  2.     NSString * MyDictionaryKey;


  3. use:


  4.      @implementation MyClass

  5.      + (NSString *) myDictionaryKey

  6.           {

  7.           return @"MyDictionaryKey";

  8.           }

  9.      @end


"That's silly", you say.


No it isn't.


This technique:


  1. a. Provides you with class-level scope for "global" values. You can reuse nice, short generic names and never worry about namespace collisions.


  2. b. Allows subclasses to override "constants" and globals. Cool. Flexible. Extensible.


  3. c. Allows easy run-time testing for availability using respondsToSelector:.


  4. d. Encourages you to create calculated "constant" values –


  5.      + (NSString *) nibName

  6.           {

  7.           return NSStringFromClass(self);

  8.           }


"But what about globals that cut across classes?"


  1. Your entire Cocoa app is a hierarchy of classes. Surely that value "belongs" to one of them. If it is a truly application-global item, it probably belongs to your NSApplication delegate, or possibly as a category on NSApplication itself.


3. Minimize mutable collection and string classes:


  1. If nothing else, such usage is much more thread-safe than the use of mutable collections.


  2. There are also a lot of optimizations in the frameworks that only apply to immutable objects. Some are a little subtle – dictionary keys are copied when added to a dictionary, but note that with an immutable key, that -copy == -retain. Thus if you ensure that you only use immutable keys, you can have a gazillion dictionary instances that use the same instance of the same key.


  3. With rare exceptions, I try to avoid using mutable instance variables and instead try to confine the scope of a mutable instance to a single method.


4. Return what you promise.


  1. If you create a method that promises to return an NSString, return an NSString, not an NSMutableString.


  2. Not only will you avoid some difficult-to-debug problems, it will make adhering to the previous strategy ("Minimize mutable collection and string classes") much more reliable.


5. Use factory methods. If they don't exist, create 'em.


  1. Instead of:


  2.      aWidget = [[widget alloc] init];

  3.      (do some stuff)

  4.   [aWidget release];


  5. or:


  6.      aWidget = [[[widget alloc] init] autorelease];

  7.      (do some stuff)


  8. Use:


  9.     aWidget = [MyWidget widget];

  10.      (do some stuff)



  11. The factory way is much cleaner, and makes it easy to use the "allocate and forget" capability of NSAutoreleasePool. Combine factory methods and immutables, and you end up with painless memory management, and fewer Inner Idiot appearances.


6. Define local autorelease pools for substantial loops that allocate stuff or call non-trivial Cocoa methods:


  1. Instead of:


  2.     while(thing = [de nextObject])

  3.           {

  4.           (do something that allocates autoreleased objects)

  5.           }


  6. Use:


  7.     NSAutoreleasePool * pool = nil;

  8.      while( thing = [de nextObject],pool = [NSAutoreleasePool new])

  9.           {


  10.           (do some stuff)

  11.          

  12.           // if you have a break in your loop...

  13.           if(breakCriterion)

  14.                {

  15.                (do something, perhaps)

  16.                [pool release];

  17.                break;

  18.                }


  19.           [pool release]; // always last statement in loop

  20.           }



  21. This is particularly important with NSDirectoryEnumerator -- it allocates a potload of autoreleased strings with each call to "nextObject". Any call to a Cocoa method can be creating auto-released objects that aren't obvious. 


  22. Even though you may find web posts nattering about the alleged inefficiency of autorelease pools, my experience has been that using local pools in loops generally significantly increases the performance of an application. This is probably because the allocator can more often re-use the same blocks inside the loop instead of having to find fresh bytes to overwrite.


  23. I wouldn't bother with this if the number of iterations is known (and small) and if the size and number of allocated objects is small.  This is, however, often not the case.


  24. This might be considered Premature Optimization by the Extreme Programming crowd, but once you get into the habit, it is a small overhead for some significant gain.


7. Understand when to use Class (+) methods:


  1. As I see it, if an instance method does not contain a reference to the instance (“self”), it isn’t really an instance method, and should be moved to the class. While this may seem like an exercise in pointless fussiness, forcing yourself to examine your methods in this light often reveals the activities of your Inner Idiot.