Swift #available 关键字与 respondsToSelector

Posted

技术标签:

【中文标题】Swift #available 关键字与 respondsToSelector【英文标题】:Swift #available keyword vs respondsToSelector 【发布时间】:2015-09-11 08:04:44 【问题描述】:

根据pre-release Swift 2 documentation,现在有一个#available 关键字可以与if letguard 语句一起用于检查API 可用性。它给出了以下示例:

let locationManager = CLLocationManager()
if #available(ios 8.0, OSX 10.10, *) 
    locationManager.requestWhenInUseAuthorization()

或者

let locationManager = CLLocationManager()
guard #available(iOS 8.0, OSX 10.10, *) else  return 
locationManager.requestWhenInUseAuthorization()

它(文档)声明相当于以下 Objective-C 代码:

if ([CLLocationManager instancesRespondToSelector:@selector(requestWhenInUseAuthorization)]) 
  // Method is available for use.
 else 
  // Method is not available.

显然respondsToSelector: 仅适用于NSObject 的子类,而#available 关键字甚至适用于“纯”Swift 代码,所以我很欣赏这种优势。

然而,自从开始 iOS 开发以来,我一直被认为对于这种情况的最佳做法是检测 API 的存在,而不是依赖于引入的版本。

作为一个更具体的例子,我正在考虑 Apple 在 iOS 7 中在 NSArray 上引入 firstObject 但追溯使其可用于 iOS 4(它可用,但私有)。任何使用 respondsToSelector: 的代码都可以在 iOS

改用我错过的#available 关键字有什么好处吗?

【问题讨论】:

@holex 它们都是运行时解决方案。 我认为原因是 Swift 不允许我们创建对函数或方法的引用(文本除外),这也很危险。在 Swift 中使用任何类型的 respondsToSelector 看起来很难看,因为您必须将方法的名称指定为字符串(也在 Obj-C 方法名称约定中):) 我不确定 #available 是一种改进,但它会帮助代码保持一致性,因为迟早会有 Swift 框架。另一方面,版本检查仅适用于系统版本,因此对于外部库,您仍然必须使用选择器检查。 @Sulthan External (Swift 2) 库可以使用 @availability 关键字注释自己的 API。作为旁注,我不确定使用if #available... 与使用NS_AVAILABLE 注释的Objective-C 方法之间的兼容性。我完全同意 respondsToSelector: 即使在 Objective-C 中也存在问题,并且必须在 Swift 中将选择器指定为字符串真是太糟糕了。我完全赞成一种更好的方式,但#available 似乎(对我来说)是一个侧步,而不是向前迈出的一步。 我也有同样的感觉。 AFAIK firstObject 方法的情况在 Swift 中是不可能的。而且因为这是不可能的,我会假设 if #available 方法适用于 Swift 中的所有情况。 【参考方案1】:

一个很大的优势是 Xcode 7 中的 Swift 2 编译器将 对部署的类、方法、属性等的可用性 项目的目标。

使用respondsToSelector 总是容易出错。对于 Objective-C,

if ([CLLocationManager instancesRespondToSelector:@selector(requestWhenInUseAuthorization)]) 

编译器仅验证requestWhenInUseAuthorization 是否为一些 已知方法,但无法验证该检查在逻辑上是否正确。

在 Swift 中情况更糟,因为选择器只能指定为 字符串,编译器不会验证任何内容。

使用 Swift 2/Xcode 7,对应代码

let locationManager = CLLocationManager()
if locationManager.respondsToSelector("requestWhenInUseAuthorization") 
    locationManager.requestWhenInUseAuthorization()

如果部署目标小于 iOS 8,则不再编译, 错误信息是

错误:“requestWhenInUseAuthorization()”仅适用于 iOS 8.0 或更高版本 locationManager.requestWhenInUseAuthorization() ^ 注意:添加 'if #available' 版本检查

let locationManager = CLLocationManager()
if #available(iOS 8.0, OSX 10.10, *) 
    locationManager.requestWhenInUseAuthorization()

编译器知道该方法只会在 iOS 8 及更高版本上调用 并且不抱怨。

【讨论】:

是的,我在文档中看到了,我想我的问题并没有真正的答案。我觉得很奇怪,他们没有提供类似于可选协议方法语法来检查 API:if locationManager.requestWhenInUseAuthorization? locationManager.requestWhenInUseAuthorization() else // method is not available @SteveWilford:if #available 不仅适用于类和方法,还适用于属性。如果我没记错的话,像 if someInstance.someOptionalProperty? ... 这样的东西在 Swift 中是不可能的。在SO或者苹果开发者论坛上有讨论,但是我没有再找到。 好点,我没有考虑可选属性。 if #someInstance.someOptionalProperty ... 开始变得愚蠢,我想这就是我不是编译器工程师的原因。也许在与#available 接触几次后,我会习惯的……【参考方案2】:

respondsToSelector 检查运行时以查看选择器是否存在。 #available 检查已发布的 SDK 是否包含在内。在 WWDC 视频中,他们明确表示,许多公共 API 在早期操作系统版本中都是作为私有 API 开始的。这意味着在某些情况下它不会是#available,即使它是respondsToSelector

【讨论】:

以上是关于Swift #available 关键字与 respondsToSelector的主要内容,如果未能解决你的问题,请参考以下文章

Swift: API 可用性检查

Swift: API 可用性检查

Parse 和 Swift:didReceiveRemoteNotification 未收到带有“aps”的推送通知:“content-available”:1

Swift 5.6 特性

Swift 5.6 特性

Swift 5.6 特性