26

I have a project that has both an iOS and an OS X target. Is there a preprocessor macro that is true when I'm compiling for OS X? I tried this:

#if TARGET_OS_MAC
@interface BFNode : NSObject <NSPasteboardReading, NSPasteboardWriting> {
#else
@interface BFNode : NSObject {
#endif

But TARGET_OS_MAC doesn't seem to work. When I try to run the app on iOS I get a compiler error, because it tries to compile the first line and there is no NSPasteboardReading protocol defined on iOS.

I know there is TARGET_OS_IPHONE too. If I use that and swap the @interface declarations it works.

But there are a lot of places where I have code, that has no iOS version, so I need a macro for OS X too.

SOLUTION:

I ended up defining a new macro in the .pch file:

#define TARGET_OSX TARGET_OS_IPHONE == 0
DrummerB
  • 39,814
  • 12
  • 105
  • 142

7 Answers7

63

The documentation in TargetConditionals.h has this diagram (it seems, as of 2022; any platform):

+---------------------------------------------------------------------+
|                            TARGET_OS_MAC                            |
| +---+ +-----------------------------------------------+ +---------+ |
| |   | |               TARGET_OS_IPHONE                | |         | |
| |   | | +---------------+ +----+ +-------+ +--------+ | |         | |
| |   | | |      IOS      | |    | |       | |        | | |         | |
| |OSX| | |+-------------+| | TV | | WATCH | | BRIDGE | | |DRIVERKIT| |
| |   | | || MACCATALYST || |    | |       | |        | | |         | |
| |   | | |+-------------+| |    | |       | |        | | |         | |
| |   | | +---------------+ +----+ +-------+ +--------+ | |         | |
| +---+ +-----------------------------------------------+ +---------+ |
+---------------------------------------------------------------------+

This tells us:

  • TARGET_OS_MAC will be 1 for (probably) any Cocoa application running on an Apple platform.

    • TARGET_OS_OSX will only be 1 for macOS targets
    • TARGET_OS_IPHONE will be 1 for any non-Mac Apple products
      • TARGET_OS_IOS is just for iOS
        • TARGET_OS_MACCATALYST is just for Project Catalyst. It seems TARGET_OS_UIKITFORMAC will also work.
      • TARGET_OS_TV is just for tvOS
      • TARGET_OS_WATCH is just for watchOS
      • TARGET_OS_BRIDGE is just for bridgeOS (which currently doesn't even support 3rd-party apps so you'll likely always see that be 0)
    • TARGET_OS_DRIVERKIT will be 1 when building for DriverKit

⚠️ But wait! ⚠️

I got that from the iOS 14 (macOS 11, watchOS 7) SDK. If I look back into the iOS 13 (macOS 10.15, watchOS 6) SDK, I see this:

+----------------------------------------------------------------+
|                TARGET_OS_MAC                                   |
| +---+  +-----------------------------------------------------+ |
| |   |  |          TARGET_OS_IPHONE                           | |
| |OSX|  | +-----+ +----+ +-------+ +--------+ +-------------+ | |
| |   |  | | IOS | | TV | | WATCH | | BRIDGE | | MACCATALYST | | |
| |   |  | +-----+ +----+ +-------+ +--------+ +-------------+ | |
| +---+  +-----------------------------------------------------+ |
+----------------------------------------------------------------+

Notably, TARGET_OS_DRIVERKIT is new in 14, and TARGET_OS_MACCATALYST is inside IOS now. This tells us that compiling against the iOS 14 / macOS 11 SDK can break some C code written for iOS 13 / macOS 10.15, if it assumes that TARGET_OS_MACCATALYST and TARGET_OS_IOS are completely separate.


There's More!

Additionally, these are defined:

  • TARGET_OS_SIMULATOR is just for iOS, tvOS, and watchOS simulators. You can further refine this using the above #defines
  • TARGET_OS_WIN32 is in case you wanna use Apple's SDKs to make Windows apps. I don't personally know of any other than Apple's own (like iTunes, Safari, and QuickTime). This might become useful now that Swift has Windows support, if you want to take existing Objective-C code with you.
  • TARGET_OS_UNIX is for non-Apple UNIX systems

And these are deprecated, and should not be used anymore. That said, you might find them in code you have to maintain, so here's what they meant:

  • TARGET_IPHONE_SIMULATOR used to mean the iPhoneOS simulator. Use TARGET_OS_SIMULATOR instead (along with TARGET_OS_IOS to target only iOS simulators)
  • TARGET_OS_EMBEDDED used to mean iOS, tvOS, and watchOS non-simulated devices. Use the standard OS targets instead.
  • TARGET_OS_NANO probably used to mean iPod Nano (I can't find any historical usage online). Apple advises to use TARGET_OS_WATCH instead.

Something else to note is that the TargetConditionals.h which is used in swift-corelibs-foundation is significantly different, and includes #defines for Android, Cygwin, and other not-explicitly-supported-but-they-technically-work platforms.

I'm not entirely sure what to make of this. I would guess it's for compiling the Swift Foundation framework, and not for consuming it, since Swift doesn't consume #defines.

Ky -
  • 30,724
  • 51
  • 192
  • 308
  • 1
    I remind you not to trust copyright infos in header files as reference dates. Apple Watch did not even exist in 2014. – erkanyildiz Mar 29 '18 at 16:53
  • `TARGET_OS_SIMULATOR` should also be mentioned perhaps. – Pavel P Feb 16 '19 at 01:15
  • 1
    @Pavel Good idea! I've added all those I thought notable – Ky - Feb 16 '19 at 05:40
  • Great!! But for C and/or C++ languages `#include ` is required (wrapped by `#ifdef __APPLE__` check), see [related post](https://stackoverflow.com/a/5920028/8740349) for more. – Top-Master Jul 12 '21 at 19:45
  • @Top-Master Since Obj-C is a strict superset of C, this will work for that as well. If you're compiling for macOS as this question asks, then you can use the `TargetConditionals.h` header (`MacOSX.sdk/usr/include/TargetConditionals.h`), which is just pure C macros. In fact it's so basic, I bet a C89 compiler would be fine handling it – Ky - Jul 12 '21 at 19:46
14

If you're using Swift, there's a great language feature for this. If you're using Objective-C, it's often useful to do something like this:

#include <TargetConditionals.h>

#if TARGET_OS_IPHONE
    @import UIKit;
#else
    @import AppKit;
#endif

Understanding the TARGET_OS_* defines will make this make a lot more sense. Most notably, TARGET_OS_MAC is any Apple platform which is pretty unexpected. !TARGET_OS_IPHONE is macOS and TARGET_OS_IPHONE is anything besides that. There are more specific defines for iOS, tvOS, and watchOS.

From TargetConditions.h:

TARGET_OS_* 
These conditionals specify in which Operating System the generated code will
run.  Indention is used to show which conditionals are evolutionary subclasses.  

The MAC/WIN32/UNIX conditionals are mutually exclusive.
The IOS/TV/WATCH conditionals are mutually exclusive.


    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

    TARGET_IPHONE_SIMULATOR   - DEPRECATED: Same as TARGET_OS_SIMULATOR
    TARGET_OS_NANO            - DEPRECATED: Same as TARGET_OS_WATCH
Sam Soffes
  • 14,831
  • 9
  • 76
  • 80
  • 1
    Note about Swift: These days, it's probably better to use [`#if canImport(UIKit)`](https://docs.swift.org/swift-book/ReferenceManual/Statements.html#ID539) – Ky - Feb 11 '21 at 00:13
8

That is because TARGET_OS_MAC is defined when building for iOS as well.

See http://sealiesoftware.com/blog/archive/2010/8/16/TargetConditionalsh.html on that.

I would try and build my own target specific define via build-settings on the target.

Till
  • 27,559
  • 13
  • 88
  • 122
4

As of Xcode 8 GM, this is the relevant section of TargetConditionals.h in iOS 10.0:

#define TARGET_OS_MAC               1
#define TARGET_OS_WIN32             0
#define TARGET_OS_UNIX              0
#define TARGET_OS_OSX               0
#define TARGET_OS_IPHONE            1
#define TARGET_OS_IOS               1
#define TARGET_OS_WATCH             0
#define TARGET_OS_BRIDGE            0
#define TARGET_OS_TV                0
#define TARGET_OS_SIMULATOR         0
#define TARGET_OS_EMBEDDED          1 
#define TARGET_IPHONE_SIMULATOR     TARGET_OS_SIMULATOR /* deprecated */
#define TARGET_OS_NANO              TARGET_OS_WATCH /* deprecated */

(In the 'breadcrumb' above the text editor in Xcode, iOS 10.0 > usr/include > TargetConditionals.h)

...And this is the same file, for macOS:

#define TARGET_OS_MAC               1
#define TARGET_OS_WIN32             0
#define TARGET_OS_UNIX              0
#define TARGET_OS_OSX               1
#define TARGET_OS_IPHONE            0
#define TARGET_OS_IOS               0
#define TARGET_OS_WATCH             0
#define TARGET_OS_BRIDGE            0
#define TARGET_OS_TV                0
#define TARGET_OS_SIMULATOR         0
#define TARGET_OS_EMBEDDED          0 
#define TARGET_IPHONE_SIMULATOR     TARGET_OS_SIMULATOR /* deprecated */
#define TARGET_OS_NANO              TARGET_OS_WATCH /* deprecated */ 

(In the 'breadcrumb' above the text editor in Xcode, macOS 10.12 > usr/include > TargetConditionals.h)

TARGET_OS_MAC is defined as 1 on both platforms, but TARGET_OS_OSX is 1 only on macOS.

In the file's comments, they are described as follows:

  • TARGET_OS_MAC: Generated code will run under Mac OS X variant.
  • TARGET_OS_OSX: Generated code will run under OS X devices.

I guess at some point back then (perhaps around when the iPhone was announced?) somebody decided that iOS (né "iPhone OS") fits the definitions of "Mac OS X variant".

Nicolas Miari
  • 16,006
  • 8
  • 81
  • 189
3

Look in TargetConditionals.h for an entire list, including:

#define TARGET_OS_MAC               1
#define TARGET_OS_IPHONE            1 
#define TARGET_IPHONE_SIMULATOR     1 
Caleb
  • 124,013
  • 19
  • 183
  • 272
  • 1
    You seem to have gotten it the wrong way around. Actually TARGET_OS_MAC is defined when building for iOS. – Till Aug 26 '12 at 19:06
1

I ran into this same situation, but needed it for Swift.

This answer here on the Apple developer forums was very helpful in dealing with the NSPasteboard protocols in your situation, but for Swift.

https://forums.developer.apple.com/thread/16757

I ended up doing:

#if os(iOS) || os(watchOS)
  public protocol TFPasteboardReading { }
  public protocol TFPasteboardWriting { }
#elseif os(macOS)
  public typealias TFPasteboardReading=NSPasteboardReading
  public typealias TFPasteboardWriting=NSPasteboardWriting
#endif


@objc(TFCategory)
public class TFCategory: TFBaseModel, TFPasteboardReading, TFPasteboardWriting {

...

}

That way TFPasteboardReading and TFPasteboardWriting is defined for macOS meaning NSPasteboardReading and NSPasteboardWriting respectively and in iOS it has no meaning, but can still be referenced in iOS. So the code compiles properly for both.

Tap Forms
  • 912
  • 8
  • 16
0

I would recommend using this:

#define TARGET_OS_OSX (!(TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH))
erkanyildiz
  • 13,044
  • 6
  • 50
  • 73
  • it was not, at that time – erkanyildiz Mar 29 '18 at 16:19
  • But you answered in 2016... and I am looking at the `TargetConditionals.h` that appears to have been written in 2014? At least that's what the copyright date says: "`Copyright (c) 2000-2014 by Apple Inc.`". – Ky - Mar 29 '18 at 16:26
  • Either way, it's bad practice to start naming things in someone else's namespace on purpose. That's how you get [#SmooshGate](https://developers.google.com/web/updates/2018/03/smooshgate). – Ky - Mar 29 '18 at 16:27
  • Yes, I answered in 2016 and I repeat it was not defined at that time. You should stop trusting copyright info in header files. – erkanyildiz Mar 29 '18 at 16:46
  • And this SmooshGate thing seems like a completely different story. This is not an implementation, just a preprocessor macro directive, which would cause compiler warnings if redefined. But if you want a similar case to SmooshGate, you should check non-prefixed category methods. – erkanyildiz Mar 29 '18 at 16:59
  • Sorry to spur any negative emotions. I'm only operating by the information I have; Apple doesn't to a good job of documenting what changes they make, when, and why. Also, the example I gave was in the spirt of demonstrating how trying to mimic an official API can lead to disaster when an official version is eventually implemented. This is why we never name our classes as prefixed with `NS`. – Ky - Mar 29 '18 at 18:54