On indie iOS and Mac development.

On Preprocessor Macros, ARC and Open Source

I get a few pull requests here and there from folks who want me to convert my blocks code to ARC. Rest assured that I will, eventually, but for now, I’m keeping it ARC agnostic so that people who have not converted to ARC can continue to use it. It’s easier to enable/disable ARC on open source code you download than it is to force people who have not converted to ARC to put the releases and retains back into that same code. Believe me, it’s as annoying for me, as it is for you that said code is not ARC. All of my projects are now ARC, and I wouldn’t code any other way. However, I too, use -fno-objc-arc on my own open source code when needed.

That said, some have suggested some alternative ways of handling open source and ARC. Most of them consist of using preprocessor macros to determine if ARC is enabled and to execute an alternative code path depending on that state. For example:

-(void)doSomething
{
    Foo *foo = [[Foo alloc] init];
    ...
#ifdef !__has_feature(objc_arc)
    [foo release];
#endif
}

I believe that this is a fundamentally wrong way to handle this situation, and I’d like to explain why.

Macros are Ugly

Macros are a very valuable tool. In fact, their purpose is to do exactly what you, young padawan are intending to do. Their purpose is to look at what type of environment your code is being compiled for, and to enable you some level of control as to what code gets compiled in that environment. However, they are also a very blunt, and error prone tool. They have no type checking and code that is cluttered with excessive macro usage is extremely hard to follow. Because of the way that macros work even compiler errors generated from macro generated code is often misleading, or downright impossible to read. Using macros all over your code like this to execute alternative code paths is fraught with peril.

In some cases, rather than sprinkling these #ifdefs all over the place in their code, some folks might simply think that they should #define their own retain/release replacements that do the switching between ARC and non-ARC for them. I just can’t bring myself to think that this is a better solution. In fact, I think it’s worse. Sprinkling MY_RELEASE(foo); all over is disconcerting to read, and will result in wasted time for other coders who not only can, but should check to see what MY_RELEASE actually does.

In addition to these, the de facto standard of naming preprocessor macros in all caps results in a codebase that more closely resembles a CHOCKLOCKed email from your crazy computer illiterate uncle than the delicate prose for which the admittedly verbose Objective-C language is known for being.

This is not to say that preprocessor macros are totally to be avoided by everyone. If you’re building an extensive framework, or if you are a systems level programmer, then you definitely have very justifiable cause for using preprocessor macros. Particularly in cases where you want to maintain compatibility across multiple platforms, or in cases where you need to help inform programmers using your code about how it needs to be used (i.e.: By having macros that show warnings for deprecations, etc.) There is probably also a small minority of programmers who might need to use macros for particularly performance critical code. If you’re one of these types of programmers, then you have my condolences.

In the case of my open source code, it does not fall into any of these categories. It’s simply one or two files, you drop into your project… and away you go. There is simply no need for using macros.

How should ARC be handled in this case?

Simple. There’s an excellent tool that the compiler provides for you. It’s the -fobjc-arc and -fno-objc-arc flags. You can set them on any file in your project just by going to the “Build Phases” part of your Xcode project and setting those as compiler flags on the files in question.

Admittedly, if your framework is very large… this could be an onerous task. In my case, however, these open source components are very small. One to two files at most.

If a user includes a non-ARC file in an ARC project, they’ll get an error when they compile it because retain and release and autorelease are deprecated. So the developer will be informed and be able to take the appropriate action. However, if you include an ARC’d file in a non-ARC project, it’ll just silently compile and leak.

To solve this, you should use a macro, but not to have conditional code paths… you should use a macro to inform the developer that they need to use ARC for that file. Here’s a simple one that I use:

#if ! __has_feature(objc_arc)
#error This file must be compiled with ARC.
#endif

This will prevent the developer from using this file unless they set the correct compiler flags. It’s simple. It’s clean. It’s out of the way, and it doesn’t clutter the code. Put this at the top of your implementation files when you convert them to ARC.

Leave a Reply