Developing a high-quality iPhone app can be tricky stuff for new and veteran developers alike - especially on larger projects. In this article, I'll discuss several tools & techniques that have helped my teams and me to produce high quality work and helped save a lot of time.

Use Multiple Build Configurations

Build configurations are an excellent way to separate configuration elements depending on the type of build you’re producing. For instance, you might want to use different names for each version of your app: Development, Staging, and Production. By giving each version a different name, you can install multiple versions of the app on the same device. I like prepending greek letters such as δ, β to the app names for different builds. Here’s a helpful visual diagram I found that shows how to change your app’s product name. Different build configurations are also useful for a number of other tips presented in this article.

To enable a new build configuration, use “Project -> Edit Project Settings.” and click on the “Configurations” tab. Pick the configuration “most like” the configuration you’d like to make. You can then make changes to your individual configurations that suit that particular build such as modifying the CFLAGs used (see the logging tip below for an example).

Use an Enhanced UIColor

The designers I work with usually express colors as hex strings, so it’d be nice to use hex strings with UIColor. The default UIColor class doesn’t have this capability, but you can fix this by adding a custom Category to UIColor.

     // file: UIColor+NFColors.h
     @interface UIColor (NFColors)

     // Create a new UIColor instance using the following long value (see
     // UIColor#colorWithHex for an example) and desired alpha value.  
     // 1.0 for opaque, 0.0 for transparent.
     // Example: Light gray color of EFEFEF [UIColor colorWithHex:0xefefef alpha:1.0]
     + (UIColor *)colorWithHex:(long)hexColor alpha:(float)opacity;
 
     @end
     
     // file: UIColor+NFColors.m
     @implementation UIColor(NFColors)
     + (UIColor *)colorWithHex:(long)hexColor alpha:(float)opacity
     {
         float red = ((float)((hexColor & 0xFF0000) >> 16))/255.0;
         float green = ((float)((hexColor & 0xFF00) >> 8))/255.0;
         float blue = ((float)(hexColor & 0xFF))/255.0;
         return [UIColor colorWithRed:red green:green blue:blue alpha:opacity];  
     }   
     @end  
  

With this category installed in your project, you can simply invoke [UIColor colorWithHex:0xefefef] to produce a light gray (#efefef) color.

Use isEmpty Instead of Nil and Length Checks

It’s amazing how often you’re looking at an object to determine if it’s empty input. Depending on the context, you might have an NSString variable that can be “nil” when not set in one place OR an empty string in another. Sometimes either nil or empty strings are acceptable “NULL” values. In these cases, you’ll want to do a test similar to this:

    if (mystring == nil || [mystring length] == 0) {
       // handle empty string
    }
 

That kind of code can get very repetitive and error-prone though. A nicer way would be to define a function that does the same thing but with out all the typing. Below is a isEmpty function that does just that. I stole it from Wil Shipley. Wil explains a number of other reasons why isEmpty is better than manually checking all over the place. If you place this function in one of your headers, it’ll be placed “inline” in your program where ever it’s used. That means there will be no extra overhead for this call — it’ll behave as fast as if you’d written this code inline yourself every where.

    // some globally used header 
    static inline BOOL isEmpty(id thing) {
        return thing == nil
        || [thing isKindOfClass:[NSNull class]]
        || ([thing respondsToSelector:@selector(length)]
            && [(NSData *)thing length] == 0)
        || ([thing respondsToSelector:@selector(count)]
            && [(NSArray *)thing count] == 0);
    }
    
    // in code later on:
    if (isEmpty(mystring)) {
        // handle empty string.
    }
  

Use Logging

Logging is a critical tool for quickly debugging apps as you build them. You can use the Objective-C’s logging function to print out application state all over your program. If you run your logging enabled app in the debugger, you can watch how the app state changes. Objective-C’s default logging statement is NSLog(NSString *, ...). You can pass a formatted string and a variable list of arguments.

NSLog is a great way to quickly see debug information about your app in real-time, but there is one problem you should be aware of — NSLog will always log your output even in production builds. If you’re using logging judiciously, then your production app will be very slow. With a little work though, we can enhance logging so that we can use it everywhere, but without the performance problems on our production builds.

A Better NSLog

You can work around the performance problems of NSLog using C macros. Using C macros, we can create a version of NSLog that’s simply not compiled into production versions of our app. Macros are “executed” at compile time and not at runtime, so we can use them to conditionally compile in or out code according to our own rules. We’ll use a C macro for our logging function that expect a macro called NF_DEBUG_ENABLED to defined when we want to log, otherwise we don’t want it to do anything. The C compiler will simply replace our macro with no operation for production builds when the NF_DEBUG_ENABLED macro value is not defined. For our debug builds, we’ll just optionally define NF_DEBUG_ENABLED so that we can see our desired debug output. One way to do this is to enable change CFlags our Development build configuration. In Xcode you can do that by changing the appropriate build configuration’s “Other C Flags” settings to include “-DNF_DEBUG_ENABLED=1”. Below is the source for a simple Logging Utility.

     #ifdef NF_DEBUG_LEVEL
     #define NFDebug( s, ... ) NSLog( @"DEBUG: %s: %@", __PRETTY_FUNCTION__, \
     [NSString stringWithFormat:(s), ##__VA_ARGS__] )
     #else
     #define NFDebug( s, ...) 
     #endif  
 

You’d use the macro just as you would use NSLog. You can even extend this idea to make different logging levels too — you can make a version for Trace, Debug, Warn, Error — as many levels as you like. This macro also the very useful side-effect that it will include the object & method name where this debug statement was printed from.

    NFDebug(@"Name is %@ and age is %d", [myobject name], [myobject age]);
    // this might produce a line in your log such as: DEBUG: [MYClass myfunction]: Name is Mike and age is 41
 

Treat Compile Warnings as Errors

Objective-C allows you to get away with a lot of things its more static cousins such as Java, C# and C++ do not. By default, many of the things you can “get away with” are simply marked as warnings and not hard errors. For instance, you can invoke selectors directly on an object even if its public interface doesn’t declare the selector. If the object actually implements the selector, there will be no runtime error. This can sometimes be a very useful feature. Unfortunately, I’ve found that on bigger teams and codebases, this also leads to many innocuous warnings that bury warnings we should really see.

I’ve found it a better policy to simply treat these warnings as errors in Xcode right from the start. That way, it becomes easier to spot warnings that are really errors. To enable this feature, enable “Treat Warnings as Errors” under “GCC x.x Warnings” in your build configuration.

Okay, you’re probably thinking — hey I actually need to access a selector dynamically, but now the compiler won’t let me because I’m treating warnings as errors! Well, there’s an option for that too. In my opinion, this option is also a more robust way of invoking unspecified selectors. You can use the respondsToSelector/performSelector option instead:

 @class Apple < NSObject {
   
 }
 - (void)consume;
 @end
 
 // apple.m
 @implementation Apple {
     // part of the "private" interface
     - (void)chopFirst {
        // ...
     }
 }
 
 // in some method somewhere:
 - (void)eatFruit:(Fruit *)fruit {
     [fruit chopFirst]; // generates compile warning / error depending on your settings
     [fruit consume];
     
     // a safer way to execute chopFirst without a compile warning / error!
   	 SEL chopFirst = @selector(chopFirst:);
     if ([application respondsToSelector:chopFirst]) {
         [application performSelector:chopFirst];
     } else { // our object doesn't handle this selector, need to fall back

     }

 }
 

The performSelector method also has a few variants that can be used if your selectors take an argument or two. If your needs are more complicated, you can also use NSInvocations for complete flexibility on how a method is dynamically invoked on an object.

Use Static Analysis Frequently

Somewhat related to treating errors as warnings, it’s very useful to run Xcode’s static analysis as frequently as you can. Though it won’t help you find virtually every memory leak like Instruments will, it will help you find a large majority of memory leaks with almost no effort. It’s well worth taking the time to run this type of build every day or two during a heavy development cycle. To perform static analysis, simply select Build and Analyze from the Build menu in Xcode.

Learn Apple’s Rules for Memory Management

Most of the time I come across memory issues in codebases, it’s because teams aren’t using Apple’s memory management rules consistently or even at all. The worst type of memory issue is are crashes where a program attempts to invoke a method on an object that’s been released already. 99% of the time, this type of issue has come down to disobeying the fundamental rule of memory management in Objective-C in some way:

You take ownership of an object if you create it using a method whose name begins with “alloc” or “new” or contains “copy” (for example, alloc, newObject, or mutableCopy), or if you send it a retain message. You are responsible for relinquishing ownership of objects you own using release or autorelease. Any other time you receive an object, you must not release it.

Apple’s documentation has several useful corollaries to this fundamental rule too. In addition to their corollaries, I have a few other useful corollaries, that apply to situations I’ve seen frequently enough:

  • Name your own methods according to the fundamental rule. If your methods are returning an object that’s now owned by its invoker, make sure your method name begins with “alloc”, “new”, or “copy.” Another plug for Static Analysis — it’ll warn you if you if you disobey this corollary!
  • In a class hierarchy, if you have more than one ivar referring to the same instance, make sure either both classes are retaining/releasing the object OR it’s clear which object owns the memory.
comments powered by Disqus