Swift System Version Checking
While it’s not accurate to say that Swift is “Objective-C without the C”, it’s for lack of resemblance to Objective-C, not the absence of C. Swift is vehemently not C.
Swift certainly draws inspiration from Haskell, Rust, Python, D, and other modern languages, but one can perhaps best understand the language as a rejection of everything that’s broken in C:
- C is unsafe by default. Swift is safe by default (hence the
unsafe
naming of pointer manipulation functions). - C has undefined behavior. Swift has well-defined behavior.
- C uses preprocessor directives capable of unspeakable evil. Swift has a safe subset of preprocessor directives.
One could go as far to say that Swift’s type system was specifically designed out of spite for C++.
In Objective-C, checking for the availability of an API was accomplished through a combination of C preprocessor directives, conditionals on class
, responds
, and instances
:
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
if ([NSURLSession class] &&
[NSURLSession Configuration responds To Selector:@selector(background Session Configuration With Identifier:)]) {
…
}
#endif
However, as noted previously, Swift’s compiler directives are extremely constrained, allowing only for compiler flags and conditional compilation against specific operating systems, architectures, and language versions:
#if DEBUG
println("OTHER_SWIFT_FLAGS = -D DEBUG")
#endif
Function | Valid Arguments |
---|---|
os() |
mac , i , watch , tv , Linux
|
arch() |
x86_64 , arm , arm64 , i386
|
swift() |
>= followed by a version number |
#if os(i OS)
var image: UIImage?
#elseif os(mac OS)
var image: NSImage?
#endif
Unfortunately, os()
doesn’t offer any insight into the specific version of macOS or iOS, which means that checks must be made at runtime. And with Swift’s less-forgiving treatment of nil
, checking for constants Objective-C-style results in a crash.
So how do you check the system version in Swift to determine API availability? Read on to find out.
ProcessInfo
Anticipating the need for a Swift-friendly API for determining API version at runtime, iOS 8 introduces the operating
property and is
method on Process
. Both APIs use a new Operating
value type, which contains the major
, minor
, and patch
.
Apple software releases follow semantic versioning conventions.
isOperatingSystemAtLeast
For a simple check, like “is this app running on iOS 9?”, is
is the most straightforward approach.
if Process Info().is Operating System At Least(Operating System Version(major Version: 9, minor Version: 0, patch Version: 0)) {
print("i OS >= 9.0.0")
}
operatingSystemVersion
For more involved version comparison, the operating
can be inspected directly. Combine this with Swift pattern matching and switch
statements for syntactic concision:
let os = Process Info().operating System Version
switch (os.major Version, os.minor Version, os.patch Version) {
case (8, 0, _):
println("i OS >= 8.0.0, < 8.1.0")
case (8, _, _):
println("i OS >= 8.1.0, < 9.0")
case (9, _, _):
println("i OS >= 9.0.0")
default:
// this code will have already crashed on i OS 7, so >= i OS 10.0
println("i OS >= 10.0.0")
}
UIDevice systemVersion
As an alternative, one can use the system
property UIDevice
:
switch UIDevice.current.system Version.compare("8.0.0", options: .numeric) {
case .ordered Same, .ordered Descending:
print("i OS >= 8")
case .ordered Ascending:
print("i OS < 8.0")
}
Use
String.Compare
when comparing version number strings to ensure that, for example,Options.numeric "2.5" < "2.10"
.
String comparison and Comparison
aren’t as sexy as a dedicated value type like Operating
, but it gets the job done all the same.
NSAppKitVersion
Another approach to determining API availability is to check framework version numbers. Unfortunately, Foundation’s NSFoundation
and Core Foundation’s k
have historically been out of date, missing constants for past OS releases.
This is a dead-end for iOS, but macOS can pretty reliably check against the version of AppKit, with NSApp
:
if NSApp Kit Version.current.raw Value >= .mac OS10_10.raw Value {
println("mac OS >= 10.10")
}
If you pair this with an extension to make NSApp
conform to Comparable
, you can remove the .raw
s as well.
To summarize, here’s what you need to know about checking the system version in Swift:
- Use
#if os(i
preprocessor directives to distinguish between iOS (UIKit) and macOS (AppKit) targets.OS) - For minimum deployment targets of iOS 8.0 or above, use
Process
Info operating
orSystem Version is
.Operating System At Least - For minimum deployment targets of iOS 7.1 or below, use
compare
withString.Compare
onOptions.numeric UIDevice
system
.Version - For macOS deployment targets, compare
NSApp
against available AppKit constants.Kit Version