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 的可用性的。
檢查類/框架的可用性
就像所有重大發(fā)布一樣,iOS 9 的發(fā)布推出了許多新的框架。如果要部署在 iOS9 以下的系統(tǒng)上,你需要以弱連接的方式使用那些新框架,并且將在運行時檢查類的可用性。比如,我們想使用 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 版本中是私有的,但現(xiàn)在是公有的了,會發(fā)生什么呢。比如,在iOS9中有UIFontTextStyleCallout在內(nèi)的幾個新的文本樣式。想要使用 iOS9 中的這種樣式,你可以試著用上面的方式檢查一下,本來預(yù)計在iOS8中應(yīng)該是空值:
if (UIFontTextStyleCallout) {
textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCallout];
}
不幸的是,這種方式并不像期待中那樣有效。結(jié)果會顯示這種符號在 iOS8 中存在,只是并不是公共聲明的。使用私有的方法或值會導(dǎo)致無法預(yù)測的結(jié)果,這并不是我們想要的。
Swift 2 方法##
Swift 中這些可用性檢查是內(nèi)置的,而且是在編譯時檢查。這意味著,當(dāng)我們使用的 API 在部署目標(biāo)系統(tǒng)不可用時,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 中執(zhí)行的代碼
}
你總是需要最后的那個 * 通配符來包含其他未指定的平臺,盡管你的 App 并不針對它們。
你可以通過以下方式來提高代碼的可讀性。如果你的代碼中有判斷語句,你可以通過在guard語句中使用#available,從而能在函數(shù)中快速返回:
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]
...
}
如果你需要一個函數(shù)或類只在某些條件下可用, 使用@available關(guān)鍵字:
@available(iOS 9.0, *)
private func checkContact() {
let store = CNContactStore()
// ...
}
編譯時安全##
最后,我們再來看這個問題。有些屬性在 iOS 9 中是公有的,但是在 iOS 8 中是私有的。如果我們在 iOS 8 中設(shè)置 iOS 9 特有的字體,會導(dǎo)致一個編譯時錯誤:
label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCallout)
> 'UIFontTextStyleCallout' is only available on iOS 9.0 or newer
Swift 可以依據(jù)平臺版本輕松進行檢測并退回到一個合理的默認(rèn)值:
if #available(iOS 9.0, *) {
label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCallout)
} else {
label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
}