Webservices Checklist

In my last job, I helped create some webservices. In the process, I learnt a thing or two about webservices. Here is a list of things that can help you create better webservices

  1. Standards compliance
  2. Consistent with one anonther
  3. Fast and Efficient
    • Response time
    • Memory allocation
    • Algorithm
  4. Load testing
  5. Validations
    • Client level
    • Server controller level
    • Server model level
  6. Request format
  7. Request validation
  8. Response format
  9. Response validation
  10. Exception handling
    • Server logging
    • Server mail alerts to developers
    • Recovery
    • Response to client
  11. Versioning
  12. Webservices update strategy
  13. Dependencies: third party frameworks and their deprecations
  14. Deprecation of webservices
  15. Scalability
  16. Documentation
  17. Security

Mac App Store Receipt Validation

When you develop a Mac app, particularly paid app, it is important to ensure that users install genuine copies of your app via the right channel. Apps distributed outside of the Mac App Store achieve this by issuing Licenses to users which they need to enter into the app before they can start using it. Apps distributed via the Mac App Store achieve this using Mac App Store receipt validation.

Every app that is downloaded from the Mac App Store is provided a receipt that resides in the app bundle at /Contents/_MASReceipt/receipt. The receipt is cryptographically signed ensuring that only Apple can create it. Developers are required to write code to validate the receipt which guarantees that the copy of the app is valid and installed via the Mac App Store. Without this check by developers, apps can be downloaded once and passed around to other users bypassing the Mac App Store. Recently, I integrated receipt validation in my Mac App. This post attempts to summarize the knowledge I acquired in the process.

Receipt validation can be done in two ways

  1. Validating with the Mac App Store: From the app, you can pass the installed receipt to your server which can perform the validation by communicating with Apple servers and notify the app about the validation result.
  2. Validating locally in the app: You can write the receipt validation code locally in the application code.

This post only covers validating receipts locally. For validating receipts with the Mac App Store, check this. It is recommended by Apple that receipt validation be performed as soon as the app is launched. The validation code should be added to the main() function before NSApplicationMain() call.

Validation Steps In Order

  1. Locate the receipt and verify that it is properly signed by Apple.
  2. Verify that the bundle identifier, version identifier strings in the receipt match the CFBundleIdentifier, CFBundleShortVersionString values respectively in the Info.plist file.
  3. Compute the hash of the GUID and verify that it matches the hash in the receipt.
  4. Note: The GUID is uniquely tied to the hardware on which the app is originally installed from the Mac App Store ensuring that copied apps don’t work due to GUID mismatch.

If all of the above steps pass, validation succeeds.

Handling Validation Failure

Sometimes it may happen that a valid install fails to write the Mac App Store receipt to the app bundle resulting in receipt validation failure when the app runs. In such cases, you should call exit with a status of 173. This exit status notifies the system that your application has determined that its receipt is invalid. At this point, the system attempts to obtain a valid receipt and may prompt for the user’s iTunes credentials.

The system relaunches the application if it successfully obtains a valid receipt. Otherwise it displays an error message to the user explaining the problem. You should not display any error message to the user on validation failure because this duty is delegated to the system. The system is responsible for trying to obtain a valid receipt or informing the user that the receipt is not valid.

Things To Remember

  • Don’t Localize Your Version Number

    If your application is localized, the CFBundleShortVersionString key should not appear in any of your application’s InfoPlist.strings files. The unlocalized value from your Info.plist file is stored in the receipt. Attempting to localize the value for this key can cause receipt validation to fail.

  • Protect Your Validation Check
    1. Inline the code for cryptographic checks instead of using the APIs provided by the system.
    2. Avoid simple code constructions that provide a trivial target for patching the application binary. For example, avoid writing code like
      if (failedValidation) {
              exit(173);
      }
          
    3. Use code obfuscation to avoid reverse engineering.
    4. Ensure that even if the exit function fails to terminate your application, your application stops running. You may use abort().

Testing Receipt Validation During Development Phase

In order to test your Mac app during the development phase, you need a valid receipt so that your application launches. To set this up, do the following

  1. Make sure you have Internet access so you can connect to Apple’s servers.
  2. Create a test user in iTunesConnect > Manage Users section.
  3. Launch your application by double-clicking on it.
  4. Your application fails to validate its receipt because there is no receipt present and exits with a status of 173.
  5. The system interprets the exit status and attempts to obtain a valid receipt by prompting you to enter your iTunes credentials. You should provide the test user’s (step 2) details here.
  6. Assuming your application signing certificate is valid, the system installs a valid receipt for the application.
  7. The system relaunches your application, and your application successfully validates the receipt.

Receipt validation is a crucial step in Mac app development process and I hope this post makes implementing it easier for someone. Here are some libraries to get you started in the right direction. GOOD LUCK AND GOOD LIFE!!!

Eclipse: Running Maven As External Tool

I am an iOS developer by profession. Recently, I created my first mac app and a website to promote it. During website development, I used Maven for managing dependencies by integrating it with Eclipse as an External Tool. Later on, I learnt about an Eclipse plug-in called m2e which makes using Maven with Eclipse a breeze. m2e uses its own distribution of Maven packaged with the plugin. But I am yet to give m2e a try.

So for those of you like me who have a standalone Maven installation and want to use it with Eclipse without installing the m2e plugin, you can do it as follows

  1. Go to Eclipse > Run Menu > External Tools > External Tools Configuration.
  2. In the left pane, select Program and click on add new configuration.
  3. Add a name for the configuration in the Name field. I have used Clean ExpenseTrack.
  4. Provide the path where the Maven executable (mvn) file is located for Location field. On my Mac, I had it at /Users/Sayeed/apache-maven-3.1.1/bin/mvn. Windows users should give <path-to-maven-installation>/bin/mvn.bat
  5. Provide the path where your project file is located for Working Directory field.
  6. Provide the mvn command for Arguments field. In this example, I have used clean.

external_tools

That’s it. You are ready to use mvn clean command by clicking Run. The command’s output gets redirected to Eclipse Console. You can add more commands following the same steps. Cheers!!!.

Mac App Store Build Validations

I recently submitted my first Mac App to the Mac App Store. Before submission, I ran a bunch of validation steps on my final production ready app. Craig Hockenberry’s blog post made it much easier. Here are the key steps

  1. Launch Terminal app and cd to the dir where the .app file is located.
  2. Recursively finds all Mach-O bundles in the app bundle.
    $ find -H Myapp.app -print0 | xargs -0 file | grep "Mach-O"
    
    Myapp.app/Contents/Frameworks/CorePlot.framework/CorePlot:                                                  Mach-O 64-bit dynamically linked shared library x86_64
    
    Myapp.app/Contents/Frameworks/CorePlot.framework/Versions/A/CorePlot:                                       Mach-O 64-bit dynamically linked shared library x86_64
    Myapp.app/Contents/Frameworks/CrashReporter.framework/CrashReporter:                                        Mach-O universal binary with 2 architectures
    Myapp.app/Contents/Frameworks/CrashReporter.framework/CrashReporter (for architecture x86_64):	            Mach-O 64-bit dynamically linked shared library x86_64
    Myapp.app/Contents/Frameworks/CrashReporter.framework/CrashReporter (for architecture i386):		    Mach-O dynamically linked shared library i386
    Myapp.app/Contents/Frameworks/CrashReporter.framework/Versions/A/CrashReporter:                             Mach-O universal binary with 2 architectures
    Myapp.app/Contents/Frameworks/CrashReporter.framework/Versions/A/CrashReporter (for architecture x86_64):   Mach-O 64-bit dynamically linked shared library x86_64
    Myapp.app/Contents/Frameworks/CrashReporter.framework/Versions/A/CrashReporter (for architecture i386):     Mach-O dynamically linked shared library i386
    Myapp.app/Contents/Frameworks/Growl.framework/Growl:                                                        Mach-O universal binary with 2 architectures
    Myapp.app/Contents/Frameworks/Growl.framework/Growl (for architecture x86_64):	                            Mach-O 64-bit dynamically linked shared library x86_64
    Myapp.app/Contents/Frameworks/Growl.framework/Growl (for architecture i386):	                            Mach-O dynamically linked shared library i386
    Myapp.app/Contents/Frameworks/Growl.framework/Versions/A/Growl:                                             Mach-O universal binary with 2 architectures
    Myapp.app/Contents/Frameworks/Growl.framework/Versions/A/Growl (for architecture x86_64):	            Mach-O 64-bit dynamically linked shared library x86_64
    Myapp.app/Contents/Frameworks/Growl.framework/Versions/A/Growl (for architecture i386):	                    Mach-O dynamically linked shared library i386
    Myapp.app/Contents/MacOS/Myapp:                                                                             Mach-O 64-bit executable x86_64

    I have colored the important ones in green. They are the bundles of interest. The others are just aliases. Certain universal (32 bit and 64 bit) bundles are shown twice but you will see only one bundle in the location pointed to. My application had only frameworks and the main executable but in your case there could be other bundles as well.

  3. Verify the Entitlements of all the bundles of interest.
    //This bundle has Entitlements
    $ codesign -d --entitlements - /Users/Sayeed/Desktop/Myapp.app/
    
    Executable=/Users/Sayeed/Desktop/Myapp.app/Contents/MacOS/Myapp
    ??qq?<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    	<key>com.apple.application-identifier</key>
    	<string>83YFQ426C5.com.app.*>/string>
    	<key>com.apple.security.app-sandbox</key>
    	<true/>
    	<key>com.apple.security.files.user-selected.read-write</key>
    	<true/>
    	<key>com.apple.security.network.client</key>
    	<true/>
    	<key>com.apple.security.print</key>
    	<true/>
    </dict>
    </plist>
    
    //This bundle doesn't have entitlements
    $ codesign -d --entitlements - /Users/Sayeed/Desktop/Myapp.app/Contents/Frameworks/CrashReporter.framework/Versions/A/CrashReporter
    
    Executable=/Users/Sayeed/Desktop/Myapp.app/Contents/Frameworks/CrashReporter.framework/Versions/A/CrashReporter
    
  4. Verify the Code signatures and Designated Requirements (DR) of all the bundles of interest.
    $ codesign --verify --verbose=4 /Users/Sayeed/Desktop/Myapp.app
    
    /Users/Sayeed/Desktop/Myapp.app: valid on disk
    /Users/Sayeed/Desktop/Myapp.app: satisfies its Designated Requirement
    
    $ codesign --verify --verbose=4 /Users/Sayeed/Desktop/Myapp.app/Contents/Frameworks/CorePlot.framework/Versions/A/Coreplot
    
    /Users/Sayeed/Desktop/Myapp.app/Contents/Frameworks/CorePlot.framework/Versions/A/Coreplot: valid on disk
    /Users/Sayeed/Desktop/Myapp.app/Contents/Frameworks/CorePlot.framework/Versions/A/Coreplot: satisfies its Designated Requirement
    Important: Xcode on Mac OS X Maverics doesn’t allow you to sign a bundle if any nested bundle in it is unsigned. These nested bundles could be things like helper executables, libraries, embedded frameworks, plug-ins, XPC services. Therefore

    1. You should sign the unsigned bundles with your own code signing identity i.e. your Developer Id Certificate or App Store Distribution Certificate.
    2. It is recommended that you re-sign the bundles that are already signed by third parties with your own code signing identity i.e. your Developer Id Certificate or App Store Distribution Certificate.

    Here is how you should do it.

  5. Verify the Identifiers, Signer Details and Trust Chain, Designated Requirements (DR) of all the bundles of interest.
    $ codesign --display --requirements - --verbose=4 Myapp.app/
    
    Executable=/Users/Sayeed/Desktop/Myapp.app/Contents/MacOS/Myapp
    Identifier=com.app.myapp
    Format=bundle with Mach-O thin (x86_64)
    CodeDirectory v=20200 size=3804 flags=0x0(none) hashes=181+5 location=embedded
    Hash type=sha1 size=20
    CDHash=4145a92202f852303290579c641dab3c36336b1f
    Signature size=4353
    Authority=3rd Party Mac Developer Application: Sayeed Hussain (83YFQ426C5)
    Authority=Apple Worldwide Developer Relations Certification Authority
    Authority=Apple Root CA
    Signed Time=13-Jun-2014 12:10:11 am
    Info.plist entries=35
    TeamIdentifier=83YFQ426C5
    Sealed Resources version=2 rules=12 files=49
    designated => identifier "com.app.myapp" and anchor apple generic and certificate leaf[subject.CN] = "3rd Party Mac Developer Application: Sayeed Hussain (83YFQ426C5)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */
    
    $ codesign --display --requirements - --verbose=4 Myapp.app/Contents/Frameworks/CrashReporter.framework/Versions/A/CrashReporter
    
    Executable=/Users/Sayeed/Desktop/Myapp.app/Contents/Frameworks/CrashReporter.framework/Versions/A/CrashReporter
    Identifier=coop.plausible.CrashReporter
    Format=bundle with Mach-O universal (i386 x86_64)
    CodeDirectory v=20200 size=1532 flags=0x0(none) hashes=69+3 location=embedded
    Hash type=sha1 size=20
    CDHash=3195a79f058becf75f663cd1fea96d9f75c7ba4b
    Signature size=4353
    Authority=3rd Party Mac Developer Application: Sayeed Hussain (83YFQ426C5)
    Authority=Apple Worldwide Developer Relations Certification Authority
    Authority=Apple Root CA
    Signed Time=13-Jun-2014 12:10:10 am
    Info.plist entries=16
    TeamIdentifier=83YFQ426C5
    Sealed Resources version=2 rules=12 files=22
    designated => identifier "coop.plausible.CrashReporter" and anchor apple generic and certificate leaf[subject.CN] = "3rd Party Mac Developer Application: Sayeed Hussain (83YFQ426C5)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */
  6. If you are building for Mac App Store skip this step. If you are distributing outside of the Mac App Store, this is the last step. Verify that Gatekeeper will accept your app.
    $ spctl --verbose=4 --assess --type execute /Users/Sayeed/Myapp.app
    
    /Users/Sayeed/Myapp.app: accepted
    source=Developer ID
    

    and you are done.

  7. If you are distributing via the Mac App Store, follow this and the next step. You need to first create an xcarchive by choosing Product > Archive in Xcode. Then, using Terminal, you need to export it as a .pkg file as shown.
    $ xcodebuild -exportArchive -exportFormat PKG -archivePath  /Users/Sayeed/Desktop/Myapp\ 13-06-14\ 12.10\ am.xcarchive -exportPath /Users/Sayeed/Desktop/Myapp -exportSigningIdentity "3rd Party Mac Developer Application: Sayeed Hussain (83YFQ426C5)" -exportInstallerIdentity "3rd Party Mac Developer Installer: Sayeed Hussain (83YFQ426C5)"
    productbuild: Adding component at /var/folders/g3/spw0ywfj4d3_rz8jbkxz4lt80000gn/T/28EB1C81-63CD-444C-96BE-9BF8DA01A34E-4171-000001432522843D/Myapp.app
    productbuild: Signing product with identity "3rd Party Mac Developer Installer: Sayeed Hussain (83YFQ426C5)" from keychain /Users/Sayeed/Library/Keychains/login.keychain
    productbuild: Adding certificate "Apple Worldwide Developer Relations Certification Authority"
    productbuild: Adding certificate "Apple Root CA"
    productbuild: Wrote product to /var/folders/g3/spw0ywfj4d3_rz8jbkxz4lt80000gn/T/28EB1C81-63CD-444C-96BE-9BF8DA01A34E-4171-000001432522843D/Myapp.pkg
    productbuild: Supported OS versions: [10.7, )
    Moving exported product to '/Users/Sayeed/Desktop/Myapp.pkg'
    ** EXPORT SUCCEEDED **
    
    

    Myapp.pkg file will get generated.

  8. Verify the Myapp.pkg file by running the installer with -store option.
    $ sudo installer -store -pkg /Users/Sayeed/Desktop/Myapp.pkg
    
    installer: Note: running installer as an admin user (instead of root) gives better Mac App Store fidelity
    installer: Myapp.pkg has valid signature for submission: 3rd Party Mac Developer Application: Sayeed Hussain (83YFQ426C5)
    installer: Installation Check: Passed
    installer: Volume Check: Passed
    installer: Bundle com.app.myapp will be installed to /Applications/Myapp.app
    installer: Starting install
    installer: Install 0.0% complete
    installer: Install 13.8% complete
    installer: Install 22.2% complete
    installer: Install 47.6% complete
    installer: Install 88.3% complete
    installer: Install 100.0% complete
    installer: Finished install
    

    and you are done.

Hope this comes handy to anyone submitting their app to the Mac App Store. That’s it for now. Good Luck And Good Life!!!

Mac App: Code Signing Frameworks

Until the introduction of Gatekeeper in Mac OS X 10.8, the concept of code signing (introduced in Mac OS X 10.5) was not so well discussed in the Mac developer community. Gatekeeper is a security feature introduced by Apple to ensure that Mac apps coming only from either App Store or from Registered Apple Developers (via their websites) are allowed to run on a User’s Mac by default. Users have the option of changing Gatekeeper settings at their own risk to allow apps from any source to run but most users don’t exercise that option.

Gatekeeper requires apps to be properly code signed. An app is considered properly code signed if the main executable and all the nested bundles (embedded frameworks, libraries, helper executables, plug-ins etc) contained in it are properly code signed.

Important: Xcode on Mac OS X Maverics doesn’t allow you to sign a bundle if any nested bundle in it is unsigned. These nested bundles could be things like helper executables, libraries, embedded frameworks, plug-ins, XPC services. Therefore

  1. You should sign the unsigned bundles with your own code signing identity i.e. your Developer Id Certificate or App Store Distribution Certificate.
  2. It is recommended that you re-sign the bundles that are already signed by third parties with your own code signing identity i.e. your Developer Id Certificate or App Store Distribution Certificate.

Code Signing Bundles The Wrong Way

Some googling would lead you to a “quick” fix on the internet to recursively sign all the nested bundles in your app. The “quick” fix is to use codesign’s –deep option in “Other Code Signing Flags” in your target’s build settings. But this solution is incorrect due to the fact that while recursively signing the nested bundles, –deep applies top-level bundle parameters like your app’s Entitlements to all the nested bundles rendering them invalid.

When users download and install such an app, Gatekeeper will notify them that it is damaged and cannot be opened. If you check the app using command line, here is what you get

$ spctl --verbose=4 --assess --type execute /Users/Sayeed/Myapp.app
/Users/Sayeed/Myapp.app: a sealed resource is missing or invalid

Code Signing Bundles The Right Way

The right way to code sign nested bundles is to add another Build Phase to your target.

When you embed frameworks, you add a “Copy Files” build phase to copy them into the Frameworks folder in your app bundle. This enables the app to run on the users’ systems because these frameworks don’t ship by default on Mac OS X. To code sign embedded frameworks after they are copied, add a Run Script Build Phase from Xcode > Editor > Add Build Phase. The Shell is bash (/bin/sh) and the script looks like

LOCATION="${BUILT_PRODUCTS_DIR}"/"${FRAMEWORKS_FOLDER_PATH}"
IDENTITY="3rd Party Mac Developer Application: Sayeed Hussain (83YFQ426C5)"
codesign --verbose --force --sign "$IDENTITY" "$LOCATION/Coreplot.framework/Versions/A"
codesign --verbose --force --sign "$IDENTITY" "$LOCATION/CrashReporter.framework/Versions/A"
#other embedded frameworks
Important: Frameworks are “versioned bundles”, and each contained version should be signed and validated separately. For example:
$ codesign -s my-signing-identity ../MyCustomFramework/Versions/A
$ codesign -s my-signing-identity ../MyCustomFramework/Versions/B
All embedded frameworks need to be signed separetly by adding the codesign command in the shell script.

If you are bulding to distribute outside of the Mac App Store, you should use your “Developer ID Application” code signing identity. If you are building for the Mac App Store, you should use your “3rd Party Mac Developer Application” code signing identity. Other embedded bundles like helper executables, plug-ins or XPC services, need to be signed appropriately after being copied into the app bundle.

Go ahead and code sign your embedded frameworks the right way. That’s it for now. Good Luck And Good Life!!!

I got an App Store Rejection

I am an iOS developer by profession. Recently, I decided to pursue an idea of my own and at the same time diversify my skill set. Ergo, I decided to build my first Mac App. So I quit my job and started working on my idea. After a few months of hard work on the Mac App and the website and a few hiccups, I was ready to submit the app to app store. Praying to god for good luck but also slightly dreading the worse, I pressed the Upload button. Three days later, lo and behold! I was staring at a REJECTI@N from Apple for these reasons:

    2.1: Apps that crash will be rejected
    2.21: Apps may not use update mechanisms outside of the App Store
    2.5: Apps that use non-public APIs will be rejected

Bad news I said to myself. But not only was I determined to push the app through, I already had the roadmap for the next version ready. There was no way I was going to let this setback slow me down even for a moment. I sat down and rectified the errors as follows:

2.1: Apps that crash will be rejected

There were two crashes noticed by Apple and they had attached the crash reports for debugging.

  1. Looking at the logs, Crash1 happened due to NSPortTimeoutException when NSSpellChecker class is used by the app for the first time after system start. I was using it to give users auto-suggestions as they start typing in a textfield. In my code, I was doing my own exception handling using the ExceptionHandling.framework. I changed my
    - (BOOL)exceptionHandler:(NSExceptionHandler *)sender shouldHandleException:(NSException *)exception mask:(NSUInteger)aMask{
            
    }
    

    method’s implemetation. That fixed it.

  2. Crash2 was related to MASRecieptValidation workflow. The app checked for the presence of a valid MacAppStore receipt stored in ExpenseTrack.app/Contents/_MASReceipt. When an app is downloaded from the Mac App Store, this receipt is installed by default and receipt validation succeeds. But while Apple was reviewing it, this receipt was absent and the app exited with exit code 173. During my testing, I created a test account on iTunesConnect to generate a dummy receipt. I said Apple that if they wanted to test the dummy receipt generation/validation workflow, they can followed these steps:
    1. Kill storeagent using killall -KILL storeagent (without this, the sign in prompt was not showing. Please refer to http://furbo.org/2013/10/21/mac-app-store-receipts-and-mavericks/ for more details).
    2. Delete, install and launch the app.
    3. The app runs its receipt validation code which fails because the receipt is absent (this won’t happen when the app is installed from Mac App Store because the receipt will be installed by default).
    4. The app quits with exit code 173 signalling storeagent to get a valid receipt.
    5. Storeagent prompts users to sign in with Apple Id and fetches and installs the receipt in the ExpenseTrack.app/Contents/_MASReceipt folder and then relaunches the app.
    6. The receipt validation code succeeds because the receipt is now present.

    That fixed it.

2.21: Apps may not use update mechanisms outside of the App Store

In the app, I had provided a Check For Updates menu option to allows users to check for updates. Apple thought that through this option I was providing an update mechanism outside of the App Store. I don’t blame them because on the face of it, it appeared so. But it wasn’t as it looked. Here is how it worked:

  1. I upload an new version to iTunesConnect and update my back-end(server) with that version number once it goes live.
  2. The app when launched makes an http request to our back-end to check for the latest available app store version.
  3. The app receives and compares it with its own version. If received version > app’s version, it generates a Growl/NSUserNotificationCenter notification which when clicked launches the App Store app on the Mac using macappstore://myappurl. This loads the app’s page in the App Store app allowing users to download the new version.

So you see, it wasn’t as it looked. But I removed the option without arguing my case any further with Apple.

2.5: Apps that use non-public APIs will be rejected

This is what Apple said, “The app includes OBJC_IVAR_$_NSView._frame from the framework ‘/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit'”. This was a little unexpected. I somehow ended up using NSView’s _frame iVar (private iVar) directly in a subclass of NSView in my code. I used otool and nm as suggested by Apple to identify the culprit class and changed the iVar usage.

After these changes I was ready to go again. I hit the Upload button once more after saying my prayers of course. Three days later, lo and behold! the app was Ready For Sale. June 16th 2014, 10:56 AM was a happy moment for me. My first Mac app had been released. All the perseverance had paid off. I thanked god, heaved a big sigh, pat myself on the back and started work on the next version. I wish everyone Good Luck And Good Life!!!

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!!!