[swift-users] Check deployment target at runtime or compile time

Greg Parker gparker at apple.com
Wed Feb 15 13:40:21 CST 2017

> On Feb 15, 2017, at 11:11 AM, Ole Begemann via swift-users <swift-users at swift.org> wrote:
> In macOS 10.12 and iOS 10.0, class properties were introduced to Objective-C [1]. I noticed that the Objective-C runtime treats class properties differently based on the deployment target. Example:
>    // MyClass is an Objective-C class that has a class property
>    let metaclass = object_getClass(MyClass.self)
>    var count: UInt32 = 0
>    let properties = class_copyPropertyList(metaclass, &count)
>    // Work with properties
>    // Deallocate properties
> When the deployment target is macOS 10.12, passing a metaclass to copyPropertyList() returns the class properties, but it returns an empty list on a lower deployment target.

That's right. The Objective-C runtime was written with the expectation that class properties would be added someday, but that code was buggy. On macOS 10.10 and iOS 8.x and older if libobjc sees a class property then it may crash. The fix was for clang and swiftc to leave class properties out of the ObjC metadata when compiling for those deployment targets. 

macOS 10.11 and iOS 9.x deployment targets are both safe. (Class properties still did not exist then. Instead the bug that nobody had seen yet was unknowingly fixed when the code was rewritten for an unrelated optimization.)

Note that class property visibility depends on the deployment target used to compile that class's implementation. It is possible to see class properties from some classes but not others if the classes come from different executables with different deployment targets.

> I'd like to perform a check (either at runtime or compile time) which of these behaviors I'll get.
> I didn't find a way to perform a compile-time check in Swift against the deployment target; if #available() checks against the SDK the code is linked against. Likewise, the usual runtime check using ProcessInfo().isOperatingSystemAtLeast() doesn't check against the deployment target.
> The best I came up with is this:
> /**
> Returns `true` if the current deployment target is at least the specified version that corresponds to the current platform.
> The arguments must be passed in the form of the `__MAC_XX_X`, `__IPHONE_XX_X`, etc. constants defined in Availability.h.
> */
> public func isDeploymentTargetAtLeast(
>    macOS macOSVersion: Int32, iOS iOSVersion: Int32,
>    tvOS tvOSVersion: Int32, watchOS watchOSVersion: Int32) -> Bool {
>    #if os(macOS)
>        return __MAC_OS_X_VERSION_MIN_REQUIRED >= macOSVersion
>    #elseif os(iOS)
>        return __IPHONE_OS_VERSION_MIN_REQUIRED >= iOSVersion
>    #elseif os(tvOS)
>        return __TV_OS_VERSION_MIN_REQUIRED >= tvOSVersion
>    #elseif os(watchOS)
>        return __WATCH_OS_VERSION_MIN_REQUIRED >= watchOSVersion
>    #else
>        return false
>    #endif
> }
> For example, to test if the current deployment target is at least macOS 10.12, iOS 10.0, tvOS 10.0, or watchOS 3.0:
> guard isDeploymentTargetAtLeast(
>    macOS: __MAC_10_12, iOS: __IPHONE_10_0,
>    tvOS: __TVOS_10_0, watchOS: __WATCHOS_3_0) else {
>    // Deployment target is lower
>    ...
> }
> Is this correct? Is there a better way to do this?

That code ought to work, assuming that the implementation of isDeploymentTargetAtLeast() and the implementations of the interrogated classes are in the same executable, or are all in executables compiled with the same deployment target.

Greg Parker     gparker at apple.com     Runtime Wrangler

More information about the swift-users mailing list