讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 9.1 設備架構與條件代碼 >

9.1 設備架構與條件代碼

在創建項目時(File→New→Project),當選擇好項目模板後,在項目命名界面上會彈出Devices菜單,提供了iPad、iPhone與Universal選項。可以稍後修改這個設置,在編輯應用目標時使用General頁簽中的Devices彈出菜單;不過如果這裡就能做出正確的決定,那麼情況會變得更加簡單,因為你的決定會影響新項目所使用的模板細節信息。在Devices彈出菜單中所做的選擇還會影響項目的Targeted Device Family構建設置:

1(iPhone)

應用可以運行在iPhone或iPod touch上;還可以運行在iPad上,但並不是作為原生iPad應用運行(它會運行在一個簡化的、可放大的窗口中,稱為iPhone模擬器;Apple有時稱為「兼容模式」)。

2(iPad)

應用只會運行在iPad上。

1,2(通用)

應用可以運行在這兩種設備上。

有兩個項目級的構建設置可以決定設備運行在什麼系統上:

Base SDK

應用可運行的最新系統。本書編寫之際,在Xcode 7.0中,你有兩個選擇:iOS 9.0與Latest iOS(iOS 9.0)。它們看起來是一樣的,但後者會更好一些(也是新項目的默認值)。如果更新Xcode來開發後續系統,那麼已經設為Latest iOS的現有項目都會自動將新系統的SDK作為Base SDK,你不必手工更新Base SDK設置。

iOS Deployment Target

應用可運行的最老的系統:在Xcode 7中,這可以一直追溯到iOS 6.0。要想修改項目的iOS Deployment Target設置,請編輯項目並切換至Info頁簽,然後從iOS Deployment Target彈出菜單中進行選擇。

9.1.1 向後兼容

如果應用的Deployment Target不同於Base SDK(也就是說,應用要兼容於老版本的系統),那就是一件很有挑戰的事情。主要會有兩個問題:

改變的行為

對於每個新系統來說,Apple都可能會改變一些特性的運作方式。結果就是不同系統上的一些特性可能會根據系統的不同而表現出不同的行為。一整塊的功能可能會在不同的系統上得到不同的處理,這要求你實現或調用不同的方法,或使用完全不同的類來處理。甚至有可能相同的方法會表現出完全不同的行為,這取決於應用運行在什麼系統上。

不支持的特性

對於每個新系統,Apple都會增加一些新特性。如果在執行時遇到了系統不支持的特性,那麼應用就會崩潰。

改變的行為是非常麻煩的事情,這裡也沒什麼更好的建議。通常,問題要麼是完全破壞的行為,要麼是先破壞,後來又修復了。比如,使用了UIProgressView的progressImage屬性的代碼在iOS 7上可以正常運行,但卻無法運行在iOS 7.1到iOS 8.4上,不過在iOS 9上又可以正常使用了。除了嘗試然後根據錯誤來修正外,別無他法,這總是非常棘手的一個問題。

在iOS 7及之前的版本中,警告視圖是通過UIAlertView呈現的,不過在iOS 8及之後的版本中變成了UIAlertController。最簡單的解決方案就是即便在iOS 8及之後的版本中也還是繼續使用UIAlertView,不過你無法保證這麼做總是可行的,因為UIAlertView在iOS 9中已經標記為不建議使用,最終可能會被丟棄掉,你還失去了使用UIAlertController的機會,它是個更棒的API。彈出框也是類似的(UIPopoverController與UIPopoverPresentationController)。這樣,系統的更迭與改進就會給開發者造成這樣一種困境:這些改進是開發者所需要的,但它們會導致向後兼容變得更加困難。

不過在Xcode 7中,編譯器至少提供了之前所沒有的一個功能,它使得代碼很難在不支持某個特性的目標系統上使用該特性。在Xcode 7之前,如果將項目的Deployment Target設為一個老系統,那麼代碼是可以編譯通過的,應用也可以運行在這個老系統之上的,即便代碼中包含了老系統上並不存在的特性亦如此;如果遇到了這些特性,那麼應用就會崩潰。在Xcode 7中,編譯器會在一開始就防止這種情況的出現。

比如:


let arr = self.view.layoutGuides
  

UIView layoutGuides屬性只存在於iOS 9.0及之後的版本中。之前,即便部署目標設為了iOS 8.0,編譯器還是會允許該代碼編譯通過;你要確保代碼絕對不能運行在iOS 8上。不過現在,編譯器會阻止你這麼做,並報錯:「layoutGuides is only available on iOS 9.0 or newer」。除非你告訴編譯器代碼只會運行在iOS 9及之後的版本中,否則是無法繼續下去的。Xcode的Fix-It特性會告訴你該如何做:


if #available(iOS 9.0, *) {
    let arr = self.view.layoutGuides
} else {
    // Fallback on earlier versions
}
  

#available條件(一個可用性檢查)會比較當前系統與聲明中所指定的特性要求。layoutGuides屬性聲明前有如下註解(在Swift中):


@available(iOS 9.0, *)

請查看文檔來瞭解該註解的具體信息。不過,你無須理解它!#available條件會匹配該註解,Xcode的Fix-It會確保如此。可以在if條件或guard條件中使用#available。

可以使用@available特性來註解自己的類型和成員聲明,代碼接下來還需要進行可用性檢查。比如,如果方法聲明使用了@available(iOS 9.0,*),那麼當部署目標早於iOS 9時,就無法調用該方法了,這裡也無須進行可用性檢查。在該方法中,無須再進行#available(iOS 9.0,*)可用性檢查,因為你已經確保該方法不會運行在iOS 9之前的系統上。

要想在老系統上測試應用,你需要一個運行著老系統的設備(物理設備或模擬器)。可以通過Xcode的Downloads首選項窗格下載iOS 8 SDK(參見第6章),不過要想測試更老版本的系統,你需要更老版本的Xcode,最好還要有一個更老的設備。請不要向App Store提交尚未在某個運行系統上測試過的應用。

9.1.2 設備類型

對於通用應用,能夠知道代碼運行在iPad還是iPhone或iPod上是很有用的。通過當前的UIDevice(或層次體系中任何UIViewController、UIView的traitCollection)可以獲得當前設備的類型並作為userInterfaceIdiom返回給你,在iPhone上是UIUserInterfaceIdiom.Phone,在iPad上則是UIUserInterfaceIdiom.Pad。

可以根據設備類型或屏幕分辨率有條件地加載資源。對於從應用包頂層加載的圖片,可以使用名字後綴,如@2x和@3x來表示屏幕分辨率,或~iphone和~ipad來表示設備類型;不過,更簡單的做法則是使用資源類別,在Xcode 7與iOS 9中,可以針對任何類型的數據資源採取這種做法。

與之類似,某些Info.plist設置帶有名字後綴,這樣就可以在一種設備類型上使用一種設置,在另外的設備類型上使用另一種設置。比如,對於通用應用,常見的做法是在iPhone上使用一套方向,在iPad上使用另一套:一般來說,iPhone版本只允許有限的方向,iPad版本則允許所有方向。可以在編輯目標時通過General窗格來配置:

1.將Devices彈出菜單切換至iPhone,並勾選iPhone上所需要的Device Orientation復選框。

2.將Devices彈出菜單切換至iPad,並勾選iPad上所需要的Device Orientation復選框。

3.將Devices彈出菜單切換至Universal。

即便現在只看到一套方向,但實際上這兩套方向都會保存起來。實際上,你所做的是在Info.plist中配置了兩組「Supported interface orientations」設置,一組通用設置(ISupportedInterfaceOrientations),一組針對iPad的設置,當應用在iPad上運行時,它會覆蓋通用設置(UISupportedInterfaceOrientations~ipad)。可以查看Info.plist文件瞭解詳情。

按照相同的方式,應用可以加載不同的nib文件,因此也會顯示不同的界面,這取決於設備的類型。比如,你可以擁有兩個主故事板,如果在iPhone上運行,那麼應用啟動時就加載其中一個;如果在iPad上運行,那就加載另一個。編輯目標時依然可以通過General窗格進行配置。實際上,你所做的是讓Info.plist設置「Main storyboard file base name」出現兩次,一次針對通用情況(UIMainStoryboardFile),另一次針對iPad(UIMainStoryboardFile~ipad)。如果應用根據名字加載nib,那麼該nib文件的命名就會像圖片文件一樣:如果運行在iPad上,並且擁有相同的名字且後綴為~ipad的nib文件,那麼它就會被自動加載。

不過,現在很少需要區分設備類型了。在iOS 7及之前的版本中,整個界面對像類(如彈出框)都只能在iPad上使用;iOS 8及後續版本中已經不存在只針對iPad的類了,如果代碼運行在iPhone上,界面類本身會自適應。與之類似,在iOS 7及之前的版本中,通用應用可能需要完全不同的界面,因此根據設備類型的不同需要不同的nib文件;在iOS 8及後續版本中,可以通過尺寸等級根據設備類型的不同有條件地配置nib文件。一般來說,應用在iPad與iPhone上的物理差別並不像過去那麼明顯:這要歸功於尺寸居於二者之間的iPhone 6,特別是iPhone 6 Plus,使得尺寸看起來更加連貫。