Checking API Availability With Swift

字数 884阅读 308

title: "使用 Swift 检查 API 可用性"
date: 2015-8-24
tags: [Swift]
categories: [Swift]
permalink: checking-api-availability-with-swift
@SwiftGG

使用 Swift 检查 API 可用性

原文链接

Swift 2 做了一些改进,能够更简单、更安全地检查 API 可否用于特定的 iOS 版本。

Objective-C 方法概述

在看 Swift 之前,我们先来简单地概述一下在 Objective-C 中,我们是如何来查看 SDK 的可用性的。

检查类/框架的可用性

就像所有重大发布一样,iOS 9 的发布推出了许多新的框架。如果要部署在 iOS9 以下的系统上,你需要以弱连接的方式使用那些新框架,并且将在运行时检查类的可用性。比如,我们想使用 iOS9 上的新的 Contacts 框架,但是也要在其不可用的时候能退回到 iOS8 上较老的 address book 框架:

if ([CNContactStore class]) {
  CNContactStore *store = [CNContactStore new];
  //...
} else {
  // Fallback to old framework
}

检查方法的可用性

使用respondsToSelector来检查框架中是否添加有该方法。比如,iOS9 在 Core Location 中引入了 allowsBackgroundLocationUpdates 属性:

CLLocationManager *manager = [CLLocationManager new];
if ([manager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) {
  // 在 iOS 8 中不可用
  manager.allowsBackgroundLocationUpdates = YES;
}

陷阱

这种检查可用性的方式很难维护,也不像它们看起来那样安全。思考一下,如果我们要检测一个符号(symbol)的可用性,这个符号(symbol)在以前 Apple 版本中是私有的,但现在是公有的了,会发生什么呢。比如,在iOS9中有UIFontTextStyleCallout在内的几个新的文本样式。想要使用 iOS9 中的这种样式,你可以试着用上面的方式检查一下,本来预计在iOS8中应该是空值:

if (UIFontTextStyleCallout) {
  textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCallout];
}

不幸的是,这种方式并不像期待中那样有效。结果会显示这种符号在 iOS8 中存在,只是并不是公共声明的。使用私有的方法或值会导致无法预测的结果,这并不是我们想要的。

Swift 2 方法##

Swift 中这些可用性检查是内置的,而且是在编译时检查。这意味着,当我们使用的 API 在部署目标系统不可用时,Xcode 能及时告诉我们。比如,如果我试着在 iOS8 中使用CNContactStore,Xcode 会提示做出如下修改:

if #available(iOS 9.0, *) {
  let store = CNContactStore()
} else {
  // 回滚到旧的版本
}

你可以使用同样的方法代替之前我们使用的respondsToSelector来进行检查:

let manager = CLLocationManager()
if #available(iOS 9.0, *) {
  manager.allowsBackgroundLocationUpdates = true
}

可用性条件

#available条件中包含了一些平台(ios,OSX,watchOS)和版本。比如,一些代码只能运行在 iOS9 或者 OS X 10.10 中:

if #available(iOS 9, OSX 10.10, *) {
  // 在 iOS 9, OS X 10.10 中执行的代码
}

你总是需要最后的那个 * 通配符来包含其他未指定的平台,尽管你的 App 并不针对它们。

你可以通过以下方式来提高代码的可读性。如果你的代码中有判断语句,你可以通过在guard语句中使用#available,从而能在函数中快速返回:

private func somethingNew() {
  guard #available(iOS 9, *) else { return }

  // do something on iOS 9
  let store = CNContactStore()
  let predicate = CNContact.predicateForContactsMatchingName("Zakroff")
  let keys = [CNContactGivenNameKey, CNContactFamilyNameKey]
  ...
}

如果你需要一个函数或类只在某些条件下可用, 使用@available关键字:

@available(iOS 9.0, *)
private func checkContact() {
  let store = CNContactStore()
  // ...
}

编译时安全##

最后,我们再来看这个问题。有些属性在 iOS 9 中是公有的,但是在 iOS 8 中是私有的。如果我们在 iOS 8 中设置 iOS 9 特有的字体,会导致一个编译时错误:

label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCallout)
> 'UIFontTextStyleCallout' is only available on iOS 9.0 or newer

Swift 可以依据平台版本轻松进行检测并退回到一个合理的默认值:

if #available(iOS 9.0, *) {
  label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCallout)
} else {
  label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
}

推荐阅读更多精彩内容