Which conditional compile to use to switch between Mac and iPhone specific code?
Asked Answered
N

5

46

I am working on a project that includes a Mac application and an iPad application that share code. How can I use conditional compile switches to exclude Mac-specific code from the iPhone project and vice-versa? I've noticed that TARGET_OS_IPHONE and TARGET_OS_MAC are both 1, and so they are both always true. Is there another switch I can use that will only return true when compiling for a specific target?

For the most part, I've gotten the files to cooperate by moving #include <UIKit/UIKit.h> and #include <Cocoa/Cocoa.h> into the precompile headers for the two projects. I'm sharing models and some utility code that fetches data from RSS feeds and Evernote.

In particular, the [NSData dataWithContentsOfURL:options:error:] function takes a different constant for the options parameter iOS 3.2 and earlier and Mac OS 10.5 and earlier than it does for iOS 4 and Mac OS 10.6. The conditional I'm using is:

#if (TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_3_2)) || (TARGET_OS_MAC && (MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5))

This seems to work, but I want to make sure this is bulletproof. My understanding is that if the Mac version is set to 10.6, but the iOS version is set to 3.2, it will still use the new constants even if it's compiling for iOS 3.2, which seems incorrect.

Thanks in advance for any help!

Nava answered 5/7, 2010 at 17:53 Comment(0)
S
71

You've made a mistake in your observations. :)

TARGET_OS_MAC will be 1 when building a Mac or iPhone application. You're right, it's quite useless for this sort of thing.

However, TARGET_OS_IPHONE is 0 when building a Mac application. I use TARGET_OS_IPHONE in my headers all the time for this purpose.

Like this:

#if TARGET_OS_IPHONE
// iOS code
#else
// OSX code
#endif

Here's a great chart on this: http://sealiesoftware.com/blog/archive/2010/8/16/TargetConditionalsh.html

Screech answered 6/7, 2010 at 0:39 Comment(3)
Unfortunately, TARGET_OS_IPHONE seems to be defined in either case if you have a project for both iOS and OSX.Vestiary
Yes. It's defined as 0 for OSX, 1 for iOS. You need to use #if TARGET_OS_IPHONE, not #ifdef TARGET_OS_IPHONE. Added an example.Screech
Btw, the chart is from Apple's "runtime wrangler." If reality seems to disagree with him, question reality. :)Screech
I
9

The macros to use are defined in the SDK header file TargetConditionals.h. Taken from the 10.11 SDK:

TARGET_OS_WIN32           - Generated code will run under 32-bit Windows
TARGET_OS_UNIX            - Generated code will run under some Unix (not OSX) 
TARGET_OS_MAC             - Generated code will run under Mac OS X variant
   TARGET_OS_IPHONE          - Generated code for firmware, devices, or simulator 
      TARGET_OS_IOS             - Generated code will run under iOS 
      TARGET_OS_TV              - Generated code will run under Apple TV OS
      TARGET_OS_WATCH           - Generated code will run under Apple Watch OS
   TARGET_OS_SIMULATOR      - Generated code will run under a simulator
   TARGET_OS_EMBEDDED       - Generated code for firmware

Since everything is a “Mac OS X variant” here, TARGET_OS_MAC is not useful in this case. To compile specifically for macOS, for example:

#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR && !TARGET_OS_EMBEDDED
    // macOS-only code
#endif

Update: Newer headers (Xcode 8+?) now have TARGET_OS_OSX defined specifically for macOS. (h/t @OldHorse), so this should work:

#if TARGET_OS_OSX
 // macOS-only code
#endif
Instinctive answered 5/7, 2016 at 16:0 Comment(1)
does not work for me. Xcode 12.4. It will simply ALWAYS use the MacOS only code.Embolden
R
8

"The correct thing to do is just use the newer constants, because if you look at the header you will see they are declared equivalent to the old ones in the enum, which means the new constants will work even on the old releases (both constants compile to the same thing, and since enums are compiled into the app they can't change without breaking binary compatibility). The only reason not to do that is if you need to continue building agains the older SDKs (that is a different thing than supporting older releases, which you can do while compiling against the newer SDKs).

If you actually wanted to use different flags based on the OS version (because the new version actually added new functionality, as opposed to just renaming a constant) then there are two sensible things you can do, neither of which your above macro accomplishes:

  1. To always use the old flags unless the min version allowed is greater than version they were introduced in (something like this):

    #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060)
      NSDataReadingOptions  options = NSDataReadingMapped;
    #else
      NSDataReadingOptions  options = NSMappedRead;
    #end
    
  2. Conditionally use only the new values in builds that can on only the new versions, and compile in code to determine the flags at runtime for builds that support both versions:

    #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060)
      NSDataReadingOptions  options = NSDataReadingMapped;
    #else
      NSDataReadingOptions  options;
      if ([[UIDevice currentDevice] systemVersion] compare:@"4.0"] != NSOrderedAscending) {
         options = NSDataReadingMapped;
      } else {
        options = NSMappedRead;
      }
    #end
    

Note that if you actually were doing this comparison a lot you would want to stash the result of the [[UIDevice currentDevice] systemVersion] compare:@"4.0"] somewhere. You also generally want to explicitly test for features using things like weak linking instead of doing version compares, but that is not an option for enums.

Retrace answered 5/7, 2010 at 18:42 Comment(2)
Thanks! This is good stuff, but I think there still might be some potential issues here. In the Build Options for the Target, there are two separate settings under Deployment, Mac OS X Deployment Target and iPhone OS Deployment Target. In both of these examples, if the Mac OS X Deployment Target is set to Mac OS X 10.6, it will use the new enum, even if you are building for iPhone OS 3.2. Is there a way, at run time or otherwise, to determine which OS is being targeted?Nava
You're confusing the settings in the Xcode inspector with what is actually sent to the compiler. Both fields are available because some types of targets (like static libraries) can be built for both platforms, but only the field relevant to the platform being built is used. You never need to determine at runtime which OS is being targeted, you know the OS at compile time (they are not binary compatible, and use different processors). You only need to determine at runtime between versions of the same OS, as single binary could be run against different versions.Retrace
A
2

The set of macros to use includes now TARGET_OS_OSX:

    TARGET_OS_WIN32           - Generated code will run under 32-bit Windows
    TARGET_OS_UNIX            - Generated code will run under some Unix (not OSX) 
    TARGET_OS_MAC             - Generated code will run under Mac OS X variant
       TARGET_OS_OSX          - Generated code will run under OS X devices
       TARGET_OS_IPHONE          - Generated code for firmware, devices, or simulator
          TARGET_OS_IOS             - Generated code will run under iOS 
          TARGET_OS_TV              - Generated code will run under Apple TV OS
          TARGET_OS_WATCH           - Generated code will run under Apple Watch OS
             TARGET_OS_BRIDGE          - Generated code will run under Bridge devices
       TARGET_OS_SIMULATOR      - Generated code will run under a simulator
       TARGET_OS_EMBEDDED       - Generated code for firmware

Seems to work ok for conditional compilation of macOS code.

Amphibolous answered 8/3, 2017 at 16:10 Comment(1)
Doesnt work for me in Xcode 12.4 either. In a generic #if #else statement it ALWAYS defaults to the iOS code, ie the else case instead of using the OSX code.Embolden
F
1

Does not apply to this Cocoa question but, for new readers, on Swift projects you can use:

#if os(macOS)
    // Compiles for macOS
#elseif os(iOS)
    // Compiles for iOS / iPadOS
#endif
Forked answered 10/7, 2023 at 10:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.