问题
According to the pre-release Swift 2 documentation there is now an #available
keyword that can be used with an if let
or guard
statement for checking API availability. It gives the following example:
let locationManager = CLLocationManager()
if #available(iOS 8.0, OSX 10.10, *) {
locationManager.requestWhenInUseAuthorization()
}
Or
let locationManager = CLLocationManager()
guard #available(iOS 8.0, OSX 10.10, *) else { return }
locationManager.requestWhenInUseAuthorization()
Which it (the doc) states is the equivalent of the following Objective-C code:
if ([CLLocationManager instancesRespondToSelector:@selector(requestWhenInUseAuthorization)]) {
// Method is available for use.
} else {
// Method is not available.
}
Obviously respondsToSelector:
only works on subclasses of NSObject
whereas the #available
keyword will work even for "pure" Swift code, so I appreciate that advantage.
However since starting iOS development I've always been led to believe that the best practice for this situation is to detect the presence of an API rather than rely on the version it was introduced.
As a more concrete example I'm thinking of when Apple introduced firstObject
on NSArray
in iOS 7 but retroactively made it available back to iOS 4 (where it was available, but private). Any code using respondsToSelector:
would have worked on iOS < 7 but obviously a version check would fail.
Are there any benefits to shifting to the #available
keyword that I've missed?
回答1:
A big advantage is that the Swift 2 compiler in Xcode 7 compares the availability of classes, methods, properties, ... against the deployment target of your project.
Using respondsToSelector
was always error-prone. For Objective-C,
if ([CLLocationManager instancesRespondToSelector:@selector(requestWhenInUseAuthorization)]) {
the compiler only verifies if requestWhenInUseAuthorization
is some
known method, but it cannot verify if that check is logically correct.
In Swift it is even worse because selectors can only be specified as strings and the compiler does not verify anything.
With Swift 2/Xcode 7, the corresponding code
let locationManager = CLLocationManager()
if locationManager.respondsToSelector("requestWhenInUseAuthorization") {
locationManager.requestWhenInUseAuthorization()
}
does not compile anymore if the deployment target is less than iOS 8, the error message is
error: 'requestWhenInUseAuthorization()' is only available on iOS 8.0 or newer locationManager.requestWhenInUseAuthorization() ^ note: add 'if #available' version check
With
let locationManager = CLLocationManager()
if #available(iOS 8.0, OSX 10.10, *) {
locationManager.requestWhenInUseAuthorization()
}
the compiler knows that the method will only be called on iOS 8 and later and does not complain.
回答2:
respondsToSelector
checks the the runtime to see if the selector is present. #available
checks the published SDK to see if it's included. In the WWDC video, they explicitly said that many public APIs start as private APIs in earlier OS versions. Which means in some cases it won't be #available
even though it respondsToSelector
来源:https://stackoverflow.com/questions/32518548/swift-available-keyword-vs-respondstoselector