Swift 2改進了檢查API可用性的方法,使其更加容易、安全。
回顧Objective-C的方法
在看Swift之前,讓我們簡要回顧一下我們之前用Objective-C檢查SDK可用性的方法。
檢查類和框架的可用性
iOS 9作為一個重要的版本,引進了許多新的框架。但如果你部署版本低於iOS 9,你需要弱連線(weak link)這些新框架,然後在執行時檢查其類的可用性。例如:如果我們想在iOS 9中使用新的聯絡人框架(Contacts framework),而在iOS 8中使用舊的通訊錄框架(AddressBook framework):
1 2 3 4 5 6 |
if ([CNContactStore class]) { CNContactStore *store = [CNContactStore new]; //... } else { // 使用舊框架 } |
檢查方法的可用性
用respondsToSelector檢查框架內是否含有此方法。例如:iOS 9在Core Location框架中新增了allowsBackgroundLocationUpdates屬性:
1 2 3 4 5 |
CLLocationManager *manager = [CLLocationManager new]; if ([manager respondsToSelector:@selector(setAllowsBackgroundLocationUpdates:)]) { // 在iOS 8中不可用 manager.allowsBackgroundLocationUpdates = YES; } |
陷阱
這些方法既難以維護,又沒有看上去那麼安全。也許某個API現在是公有的,但在早期的版本中卻有可能是私有的。例如:iOS 9中新增了幾個文字樣式,如UIFontTextStyleCallout。如果只想在iOS 9中使用這種樣式,你可以檢查其是否存在,因為它在iOS 8中應該是null的:
1 2 3 |
if (UIFontTextStyleCallout) { textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCallout]; } |
不幸的是結果並非如此。原來這個標誌在iOS 8中是存在的,只是沒有宣佈公有。使用一個私有的方法或值有可能出現難以預料的結果,況且這也和我們的想法不同。
Swift 2的方法
Swift 2內建了可用性檢查,而且是在編譯時進行檢查。這意味著當我們使用當前部署版本不可用的API時,Xcode能夠通知我們。例如:如果我在部署版本為iOS 8的情況下使用CNContactStore,Xcode將提出以下改進:
1 2 3 4 5 |
if #available(iOS 9.0, *) { let store = CNContactStore() } else { // 舊版本的情況 } |
同樣這可以取代我們之前使用的respondsToSelector:
1 2 3 4 |
let manager = CLLocationManager() if #available(iOS 9.0, *) { manager.allowsBackgroundLocationUpdates = true } |
可用性檢查的使用情形
#available條件適用於一系列平臺(iOS, OSX, watchOS) 和版本號。例如:對於只在iOS 9或OS X 10.10上執行的程式碼:
1 2 3 |
if #available(iOS 9, OSX 10.10, *) { // 將在iOS 9或OS X 10.10上執行的程式碼 } |
即使你的App並沒有部署在其他平臺,最後也需要用*萬用字元來包括它們。
如果某塊程式碼只在特定的平臺版本下執行,你可以用guard宣告配合#available來提前return,這樣可以增強可讀性:
1 2 3 4 5 6 7 8 |
private func somethingNew() { guard #available(iOS 9, *) else { return } // 在iOS 9中執行的程式碼 let store = CNContactStore() let predicate = CNContact.predicateForContactsMatchingName("Zakroff") let keys = [CNContactGivenNameKey, CNContactFamilyNameKey] ... } |
如果整個方法或類只在特定的平臺版本下存在,用@available:
1 2 3 4 5 |
@available(iOS 9.0, *) private func checkContact() { let store = CNContactStore() // ... } |
編譯時的安全性檢查
結束前,讓我們再看看那個常量在iOS 9中公有卻在iOS 8中私有的問題。如果部署版本為iOS 8,我們卻把字型設定為一個只有iOS 9才能用的樣式,這將產生一個編譯錯誤:
1 2 |
label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCallout) > 'UIFontTextStyleCallout' is only available on iOS 9.0 or newer |
Swift使其便於除錯,同時能夠根據平臺版本賦一個合理的值:
1 2 3 4 5 |
if #available(iOS 9.0, *) { label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleCallout) } else { label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody) } |