I recently wrote a blog post about redefining some macros present in system headers to generate new api usage warnings in Xcode. This is helpful when your Mac app’s deployment target is lower than your XCode SDK version. Here is how the code snippet looked
// MyProject-Prefix.pch // Prefix header // The contents of this file are implicitly included at the beginning of every source file. #ifdef __OBJC__ #import <Foundation/NSObjCRuntime.h> #if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_7 #undef AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER #ifdef __clang__ #define AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER __attribute__((weak_import, deprecated("API is newer than Deployment Target."))) #else #define AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER WEAK_IMPORT_ATTRIBUTE #endif #endif #undef NS_CLASS_AVAILABLE #define NS_CLASS_AVAILABLE(_mac, _ios) AVAILABLE_MAC_OS_X_VERSION_##_mac##_AND_LATER #undef NS_ENUM_AVAILABLE #define NS_ENUM_AVAILABLE(_mac, _ios) AVAILABLE_MAC_OS_X_VERSION_##_mac##_AND_LATER #import <Cocoa/Cocoa.h> #endif
Let’s dissect this code snippet a little. Let’s talk about why putting this
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_7 . . . define NS_ENUM_AVAILABLE(_mac, _ios) AVAILABLE_MAC_OS_X_VERSION_##_mac##_AND_LATER
in that order is important.
In the .pch file, <Cocoa/Cocoa.h> file internally imports <Foundation/Foundation.h> which inturn imports <Foundation/NSObjCRuntime.h> which contains the orignal macros that we are attempting to redefine. So why are we importing <Foundation/NSObjCRuntim.h> again on top? It is required because unless we import it before <Cocoa/Cocoa.h> and redefine the macros in question, the macro redefinitions won’t apply to the headers in <Cocoa/Cocoa.h> which is imported subsequently. As a result no warnings will get generated by Xcode at all. Hence the import of <Foundation/NSObjCRuntime.h>, redefinition, <Cocoa/Cocoa.h> in that order is important.
Ergo with the redefined macro replacements in place for the <Cocoa/Cocoa.h> that follows, any reference to new api symbols in your source code generates an Xcode warning. But why not make new api reference an error? The answer is that after factoring in the unavailability on older OS X versions, you may want to use the new apis conditionally and hence the warning mechanism seems like a better fit.
You may ask: What happens when we include <Foundation/NSObjCRuntime.h> explicitly and it get’s implicitly included in <Cocoa/Cocoa.h> as stated above? Well, that’s what the #import directive is there for. The #import directive ensures that a file is included only once no matter how many times you put the #import statement. So the <Foundation/NSObjCRuntime.h> in <Cocoa/Cocoa.h> gets ignored. StackOverflow has a nice post on this.
Redefining a macro the right way
There are two scenarios where a macro collision like situation arises:
- The one that we saw above. Just comment out the line #undef AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER in the above code snippet and Xcode starts throwing macro redefined warning.
- When macros with same name but different definitions start falling in the same namespace by virtue of an #import. Let’s say you define a macro mysupermacro in abc.m and in pqr.h with different definitions. If you import pqr.h in abc.m, you get a macro redefinition warning. If you ignore the warning, the later definition gets applied. If the namespaces don’t collide (say you don’t import pqr.h in abc.m but in xyz.m and use mysupermacro there), mysupermacro serves its purpose in their respective files according to its definitions.
To summarize, the order of #import can be important sometimes and this case is a good example. That’s it for now. Good Luck And Good Life!!!