Category Archives: Xcode

Order of #import and Xcode new api usage warnings

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

#import <Foundation/NSObjCRuntime.h>

then

#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

and then

#import <Cocoa/Cocoa.h>

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:

  1. 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.
  2. 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!!!

Xcode New API Usage Warning: SDK vs. Deployment Target

Update: This no longer works on Xcode SDK 10.10 due to changes by Apple to <Availability.h> If I find a new solution, I will update this post.

We know that Xcode warns us about deprecated API usage i.e. if we use an api in our code that was available in older versions of the OS X SDK but is now marked deprecated (and may disappear sometime in future) we get a warning. The warning is to let us know that we need to fix the deprecated API usage because in future when the API is removed, any reference to the API will result in BOOM!!! Cr@s*h**!!!. So far so good right.

But what happens when we want to support our app on older versions of OS X but we upgrade our Xcode which includes a newer OS X SDK. Let’s say we want to run our Mac app on OS X 10.7 (MACOSX_DEPLOYMENT_TARGET) onwards but the Xcode version we are using now for development comes with OS X 10.8 SDK. There are going to be certain new APIs (Methods / Classes / Enums) made available in 10.8. If we use these newer APIs in our code, the compilation will go on fine without Xcode giving any build warning about unavailability on 10.7 (MACOSX_DEPLOYMENT_TARGET) but when the app is run on OS X 10.7 and these new API symbols are referenced BOOM!!! Cr@s*h**!!!.

This leads us to our problem statement:

How do we generate warnings while using new API’s when our Deployment Target (Min OS X version we want to support) is lower than the Xcode SDK Version?

The original solution was given by Jeff Johnson here. The solution is to redefine certain <AvailabilityMacros.h> macros so that they start generating warnings when newer API’s are used. Adding the following code in our prefix header (.pch file) does the trick:

#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
  1. Redefining AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER takes care of methods that are introduced in 10.8 which are marked as NS_AVAILABLE() or NS_AVAILABLE_MAC() in the headers.
  2. Redefining NS_CLASS_AVAILABLE takes care of classes that are introduced in 10.8 which are marked as NS_CLASS_AVAILABLE() or NS_CLASS_AVAILABLE_MAC() in the headers.
  3. Redefining NS_ENUM_AVAILABLE takes care of methods that are introduced in 10.8 which are marked as NS_ENUM_AVAILABLE() or NS_ENUM_AVAILABLE_MAC() in the headers.

We are generating warnings because we want to conditionally use the newer API’s when running on 10.8 after factoring in the non availability on 10.7. To suppress the warnings after factoring in the non-availability, we need to use #pragma directives as shown below:

Class notifCenter = NSClassFromString(@"NSUserNotificationCenter");
if(notifCenter){//NSUserNotificationCenter class available. This is the factoring in of non availability on 10.7.
     #pragma clang diagnostic push
     #pragma clang diagnostic ignored "-Wdeprecated-declarations"
        NSUserNotification *notification = [[[NSUserNotification alloc] init] autorelease];
        [notification setTitle:@"My Notification"];
        [NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];        
     #pragma clang diagnostic pop
}

Note: This solution needs to be modified when the MACOSX_DEPLOYMENT_TARGET and / or the SDK changes.


Related Readings:
Importance of order of #import, Weak Linking Symbols