Appropriate Use of C Macros for Objective-C Developers
I’ve been writing software for iOS full time for almost 3 years. During that time, I’ve noticed an increasing number of developers using C macros for operations where they’re not necessary, and could be more effectively replaced with either C functions or Objective-C methods.
C macros come with a host of problems that are well known to C and C++ programmers. There are situations where they provide unique functionality, but those situations are rare. My rough sense is that many members of the exploding iOS community are coming from a Java, Python, or Ruby background, and don’t have deep experience with a C-based language. Many of these developers don’t have a thorough understanding of how easy it is for macros to cause subtle and unexpected problems. There’s nothing wrong with this; learning the nooks and crannies of an archaic language like Objective-C takes time. It takes making lots of mistakes. Hopefully I can help a few people avoid some that I’ve tripped over in the past.
Macros are preprocessor definitions. What this means is that before your code is compiled, the preprocessor scans your code and, amongst other things, substitutes the definition of your macro wherever it sees the name of your macro. It doesn’t do anything more clever than that. Almost literal code substitution. There are plenty of great explanations online of the kinds of subtle and not-so-subtle problems they can cause. I’ll give one extremely simple example here.
Suppose you want a method to return the maximum of two numbers. You write a macro to do this simple task:
#define MAX(x, y) x > y ? x : y
Simple, right? You then use the macro in your code like this:
int a = 1, b = 2;
int result = 3 + MAX(a, b);
Looks innocent. The problem is that the preprocessor substitutes the macro definition into the code before compilation, so this is the code the compiler sees:
int a = 1, b = 2;
int result = 3 + a > b ? a : b;
C order of operations requires the sum 3 + a be calculated before the ternary operator is applied. You intended to save the value of 3 + 2 in result, but instead you add 3 + 1 first, and test if the sum is greater than 2, which it is. Thus result equals 2, rather than the 5 you expected.
Already alarm bells should be going off. But you’ve come this far, so you fix the problem by adding some parentheses and try again:
#define MAX(x, y) ((x) > (y) ? (x) : (y))
Seems to work now. You breath an uneasy sigh of relief. You continue along on your project until you have this code:
int a = 1, b = 2;
int result = MAX(++a, ++b);
When the preprocessor runs, this expands to:
int result = ((++a) > (++b) ? (++a) : (++b));
Parentheses can’t save you now. Whichever value is greater is going to be incremented twice, not once. This is almost certainly not what you intended. Now you know you’re doing something wrong, and you start to look around for alternatives.
So what’s a better way? It’s simple. Just write a function. Functions are easy to write, easy to read, state your intent clearly, and the compiler has all kinds of optimization tricks it can apply if appropriate. A max function is trivially written as:
int max(int x, int y)
{
return x > y ? x : y;
}
Done. No subtle errors, no seemingly random bugs causing you to rip your hair out, bashing your head against the keyboard, ruing the day you decided to become a programmer. And for Objective-C developers, rest assured that there’s no reason you can’t mix in C functions into your code. If you’ve ever written any CFNetworking or Quartz 2D code, you’re calling C methods.
Don’t Use Macros to Define Constants
Probably the most common macro use is to define a constant:
#define NUM_THREADS 4
Here it’s better to use a static const integer:
static const NSInteger NUM_THREADS = 4;
The advantages over the macro are that the value of the variable can be viewed in the debugger, it respects scope according to the rules of the language, and it’s type-safe.
Prefer Functions or Categories over Macros for Helper Methods
Some people tote around header files full of frequently used macros, most of which should probably be functions or class categories. Consider detecting whether your app is running on an iPad. You can write this as a macro like so:
#define IS_PAD [[UIDevice currentDevice] interfaceIdiom] == UIUserInterfaceIdiomPad)
Make no mistake, though, that this brings along all the subtle problems of macros generally as demonstrated above.
One way to rewrite this is simply as a category on UIDevice (I’m only showing the implementation portion):
@implementation
- (BOOL)isPad
{
return [self interfaceIdiom] == UIUserInterfaceIdiomPad;
}
@end
Then it can be used easily:
if ([[UIDevice currentDevice] isPad])
// my iPad specific code
Or the macro can easily be rewritten as a C function:
(BOOL)isPad()
{
return [[UIDevice currentDevice] interfaceIdiom] == UIUserInterfaceIdiomPad;
}
Both are simple and clear, respect scope, are type-safe, etc. Unless you have some extremely special case, this should be the preferred way to write helper methods.
Appropriate Use of Macros
There are occasions where macros provide necessary functionality not available through other means. One example is when you do need to actually have the context of the current code location at hand when the code executes.
For example, I have a simple macro I sometimes use when debugging Objective-C to log when certain methods are called. This can be done like so:
NSLog(@"%@: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
This can’t be moved into an Objective-C method because it will always log the name of that method. (Obviously it can’t be moved into a C method, as there is no self and no _cmd variables available.) Creating a macro for this is straightforward, however:
#define LOG_SELECTOR() NSLog(@"%@: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
In a case like this, the only option is to use macros. That’s fine. Macros are a tool in one’s tool belt, so to speak. It’s great to use them when appropriate. But it’s important to understand their limitations, and to know what alternatives are available and why they should be preferred.



