讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 6.5 從項目到運行應用 >

6.5 從項目到運行應用

應用文件實際上是一種特殊的目錄,叫作包(package,而特殊的package則叫作bundle)。通常情況下,Finder會將包當作文件,並不會將其內容顯示給用戶,但你可以繞過這種防護措施並使用Show Package Contents命令查看應用包的內容。這樣就可以瞭解到應用包的結構了。

圖6-14:在Finder中查看構建好的應用

我們將使用之前構建的Empty Window應用作為示例應用來一探究竟。你需要在Finder中找到它;默認情況下,它應該位於用戶的Library/Developer/Xcode/DerivedData目錄下,如圖6-14所示。

在Finder中,按下Control鍵並單擊Empty Window應用,從上下文菜單中選擇Show Package Contents。你會看到構建過程的結果(如圖6-15所示)。

圖6-15:應用包的內容

可以將應用包看作項目目錄的一種變換:

Empty Window

應用編譯後的代碼。構建過程會將ViewController.swift與AppDelegate.swift文件編譯到這個文件中,即應用的二進制文件。它是應用的核心,實際執行的內容。當應用啟動時,該二進制文件會被鏈接到各種框架上,代碼會開始運行(本章後面將會詳細介紹「開始運行」所涉及的東西)。

Main.storyboardc

應用的界面故事板文件。項目的Main.storyboard就是應用界面的來源。在該示例中,一個空白視圖會佔據整個窗口。構建過程會將Main.storyboard編譯為更加緊湊的格式(使用ibtool命令行工具),即.storyboardc文件,它實際上包含了多個nib文件,當應用啟動時會按需加載。其中一個nib文件會在應用啟動時加載進來,它就是界面中所顯示的空白視圖的來源。Main.storyboardc與項目目錄中的Main.storyboard位於同一個子目錄中(在Base.lproj中);如前所述,該目錄結構與本地化有關(第9章將會介紹)。

LaunchScreen.storyboardc

應用的啟動界面文件。該文件(LaunchScreen.storyboard的編譯版本)包含了應用啟動的短暫時間內所顯示的界面。

Assets.car、[email protected][email protected]

資源目錄與一對圖標文件。在構建準備時,我向原來的資源目錄Images.xcassets中添加了一些圖標圖片和其他一些圖片資源。該文件處理後(使用actool命令行工具)會生成一個編譯後的資源目錄文件(.car),它包含了添加到目錄中的所有資源。同時,圖標文件會被寫到應用包的頂層,系統會在這裡尋找它們。

Info.plist

這是個遵循嚴格文本格式的配置文件(屬性列表文件)。它來自於項目的Info.plist,但與之並不完全相同。其包含的指令用於告訴系統該如何對待並啟動應用。比如,項目的Info.plist有一個計算後的報名,它來自於產品的名字$(PRODUCT_NAME);在構建後的應用的Info.plist中,計算會執行,值會讀取Empty Window。此外,它還會連同資源目錄一同將圖標文件寫到應用包的頂層,這時會向構建好的應用的Info.plist中添加一項設置,告訴系統這些圖標文件的名字是什麼。

Frameworks

有幾個框架會添加到構建好的應用中。我們的應用使用了Swift;這些框架會包含完整的Swift語言!應用所用的其他框架會被構建到系統中,但Swift不會。將Swift框架打包到應用包中能夠允許Apple快速演化Swift語言,同時又獨立於任何系統版本,並且還可以讓Swift向後兼容老系統。其副作用就是這些框架會增加應用的大小;不過,相比於Swift的強大與靈活性,這麼做是值得的。(也許未來,當Swift語言穩定下來後,它會被構建到系統而不是每個應用中,這樣Swift應用就會變得小一些。)

PkgInfo

這是一小段文本,內容是APPL????,表示該應用的類型和創建者代碼。PkgInfo文件已經過時了;對於iOS應用的功能來說並沒有什麼用,並且是自動生成的。你永遠不會用到它。

在實際開發中,應用包可能會包含多個文件,但差別主要還是量而不是種類的問題。比如,我們的項目可能會有額外的.storyboard或.xib文件、框架或聲音文件等資源。所有這些文件都會按照自己的方式放到應用包當中。此外,在設備上運行的應用包還會包含一些安全相關的文件。

你現在應該能體會到這種項目組件的處理方式以及組裝到應用中的方式所帶來的好處了,同時也清楚為了確保應用能夠正確構建,程序員應該做哪些事情。本節後面的內容將會介紹項目中的哪些內容會被放到應用的構建中,以及應用的構成是如何使得應用能夠運行起來的。

6.5.1 構建設置

我們已經介紹了該如何使用構建設置。Xcode本身、項目以及目標都可以修改最終的構建設置值,根據構建配置的不同,其中一些可能會有所不同。在構建前,你需要指定好方案;方案會決定構建配置,這樣在構建時特定的構建設置值才會應用上。

6.5.2 屬性列表設置

項目中會包含一個屬性列表文件,它用於生成構建的應用的Info.plist文件。項目中的這個文件不一定非得叫作Info.plist!應用目標知道該文件是什麼,因為其名字位於Info.plist文件的構建設置中。比如,在我們這個項目中,應用目標的Info.plist文件構建設置值被設為了Empty Window/Info.plist(看看構建設置就知道了)。

屬性列表文件是個鍵值對的集合。你可以編輯它,有時也需要這麼做。編輯項目的Info.plist主要有3種方式:

·在項目導航器中選中Info.plist文件並在編輯器中對其進行編輯。在默認情況下,鍵名(以及一些值)會通過一些描述性信息顯示,這是由其功能決定的;比如,鍵名可能用「Bundle name」表示而非實際的鍵CFBundleName。不過可以通過在編輯器中單擊,然後選擇Editor→Show Raw Keys & Values或使用上下文菜單來查看實際的鍵。

此外,可以通過實際的XML格式來查看和編輯Info.plist文件:按住Control並在項目導航器中單擊Info.plist文件,並從上下文菜單中選擇Open As→Source Code。(不過,以原生XML形式編輯Info.plist是有風險的,因為一旦出錯,XML就無效了,這會導致出現問題,但卻看不到警告。)

·編輯目標並切換至信息窗格。Custom iOS Target Properties部分會顯示出與在編輯器中編輯Info.plist時相同的信息。

·編輯目標並切換至General窗格。這裡的一些設置可用於編輯Info.plist。比如,單擊Device Orientation復選框會改變Info.plist中「Supported interface orientations」鍵的值。(這裡的其他一些設置可用於編輯構建設置。比如,如果修改了Deployment Target,那就會修改iOS Deployment Target構建設置的值。)

項目Info.plist中的一些值會被處理以將其轉換為構建好的應用的Info.plist中的最終值。這個步驟會在構建過程後期進行。比如,項目Info.plist中的「Executable file」鍵值是$(EXECUTABLE_NAME);它會被EXECUTABLE_NAME構建環境變量的值所替換(可以通過Run Script構建階段查看它)。此外,在處理過程中還會向Info.plist注入一些額外的鍵值對。

若想瞭解鍵的完整列表及其含義,請參見Apple的文檔:Information Property List Key Reference。第9章將會介紹Info.plist中你很有可能會修改的一些設置。

6.5.3  nib文件

nib文件是以一種編譯好的格式對用戶界面的描述,它包含在一個擴展名為.nib的文件中。你所編寫的每個應用都至少包含一個nib文件。通過在Xcode中以圖形化方式編輯.storyboard或.xib文件來準備這些nib文件;實際上,你正在設計一些對象,這些對像會在應用運行以及nib文件加載時實例化。

nib文件是在構建過程中生成的,要麼通過編輯.xib文件(使用ibtool命令行工具),這會生成一個nib文件;要麼通過編輯.storyboard文件,這會生成包含了多個nib文件的.storyboardc包。編輯是對.storyboard或.xib文件進行的,它們位於應用目標的Copy Bundle Resources構建階段中。

Single View Application模板所生成的Empty Window項目包含了一個名為Main.storyboard的界面.storyboard文件。這個文件會被特殊對待,它是應用的主故事板;之所以是這樣並非因為它的名字,而是因為它被Info.plist文件中的鍵「Main storyboard file base name」(UIMainStoryboardFile)所指向;使用其名字(「Main」)並去掉.storyboard擴展來編輯Info.plist文件就會看到了!結果是,當應用啟動時,從.storyboard文件中所生成的第1個nib會自動加載以幫助創建應用的初始界面。

本章後面將會詳細介紹應用啟動過程與主故事板。請參考第7章以瞭解關於編輯.storyboard與.xib文件,以及代碼運行時它們是如何創建實例的更多信息的。

6.5.4 其他資源

資源是嵌入應用包中的輔助文件,當應用啟動時會根據需要獲取,比如,想要展示的圖片或想要播放的聲音等。實際應用很可能會包含多個附加資源。當應用運行時確保這些資源可用通常取決於你的代碼(或加載nib文件的代碼):基本上,運行時只是進入應用包中,然後拉取所需的資源。實際上,應用包會被看作充滿了很多東西的目錄。

可以在兩個地方向項目添加資源,這對應於兩個不同的位置,都位於應用包當中:

項目導航器

如果向項目導航器添加了資源,那麼還要確保它會出現在Copy Bundle Resources構建階段中,它會被構建過程複製到應用包的頂層。在圖6-15中,它與圖標圖像文件處於同一層級,如[email protected]

圖6-16:向項目添加資源時的選項

資源目錄

如果向資源目錄添加了資源,那麼當構建過程向應用包的頂層複製並編譯資源目錄時(就像圖6-15中的Assets.car),資源就會位於其中。

後面將會介紹這兩種向項目添加資源的方式。

1.項目導航器中的資源

要想通過項目導航器向項目添加資源,請選擇File→Add Files to[Project];還可以將資源從Finder拖曳到項目導航器中。無論哪種方式都會出現一個對話框(如圖6-16所示),你可以做如下設置:

目標

你幾乎總是應該勾上這個復選框(「Copy items if needed」)。這麼做會將資源複製到項目目錄中。如果不勾選這個復選框,那麼項目就會依賴於項目目錄外的文件,你可能會不小心刪除或修改它。請將項目所需的一切文件放到項目目錄中。

添加目錄

只有向項目中添加的是目錄時該選項才會起作用;區別在於項目引用目錄內容的方式:

創建分組

目錄名會成為項目導航器中普通分組的名字;目錄內容會出現在該分組中,不過它們會列在Copy Bundle Resources構建階段中,這樣在默認情況下,它們都會被複製到應用包的頂層。

創建目錄引用

在項目導航器中,目錄顯示為藍色(一個目錄引用);此外,它會作為目錄顯示在Copy Bundle Resources構建階段中,這意味著構建過程會將整個目錄及其內容複製到應用包中。目錄中的所有資源都不會位於應用包的頂層,而是在一個子目錄中。如果有很多資源,並且想要對其分門別類(而非將所有資源都放在應用包的頂層),或目錄層次對於應用是有意義的,那麼這種佈局就很有價值了。這種佈局的副作用就是你所編寫的用於訪問資源的代碼將會特定於包含該資源的目錄的子目錄。

添加到目標

選中該復選框會將資源添加到目標的Copy Bundle Resources構建階段中。這樣,大多數情況下都需要針對應用目標將其選中;為何需要將資源添加到項目中呢?如果不小心未選中該復選框,稍後發現項目導航器中所列出的資源需要針對某個特定的目標被添加到Copy Bundle Resources構建階段中,那麼你可以手工添加,具體做法如前所述。

2.資源目錄中的資源

在Xcode 7之前,資源目錄只是用於圖片文件。其他資源(如音頻文件等)只能在項目導航器中添加。在Xcode 7中,資源目錄可以包含任何種類的數據文件。還可以通過資源目錄指定一個資源的不同版本以提供給不同硬件配置,比如,設備的屏幕分辨率(對於圖片),或iPhone與iPad(對於任何類型的資源)。

對於圖片文件來說,資源目錄可以幫助你輕鬆區分圖像文件名特殊約定上的差別。比如,由於iOS 9可以運行在一倍分辨率、兩倍分辨率及三倍分辨率的設備上,因此需要為每個圖片都提供3種尺寸。為了能夠與框架的圖片加載方法協同工作,這種資源都使用了特殊的命名約定:比如,listen.png、[email protected][email protected]。項目導航器中圖片文件數量的增長速度會非常快,也很容易出錯。資源目錄就是為了緩解這個問題而出現的。

相對於在添加到項目時手工單調地命名listen.png文件的多個版本,我可以讓資源目錄幫助我。編輯資源目錄,單擊第1列底部的+按鈕,選擇New Image Set。結果是一個名為Image的圖片集,它帶有3個不同尺寸的圖片。我將圖片從Finder拖曳到恰當的地方。原始圖片文件名並不重要!圖片會被自動複製到項目目錄中(位於資源目錄中),無須指定這些圖片文件的目標成員,因為它們都是資源目錄的一部分,已經擁有了正確的目標成員。我可以重命名圖片集,將其改為更具描述性的名字,比如,叫它listen。結果是代碼現在可以針對當前的屏幕分辨率加載正確的圖片,方式是通過"listen"引用它,不用管圖片的原始名或擴展是什麼。

可以通過選中一張圖片,然後使用屬性查看器(Command-Option-4)來查看資源目錄中的圖片。這會顯示出原始名與圖片的像素大小(這一點更為重要)。

在Xcode 7中,類似的處理過程也適用於其他類型的資源。假設我想要向應用包中添加一個名為Theme.mp3的音頻文件。我會編輯資源目錄,單擊+按鈕,並選擇New Data Set。這時,一個名為Data的數據集會出現,它有一個Universal位置,我現在就可以將音頻文件拖曳進去了。我對數據集進行了重命名(改為了theme);現在,我的代碼可以通過名字"theme"來訪問該資源了(通過iOS 9新增的NSDataAsset類)。

此外,資源目錄中的目錄可用於提供命名空間:比如,如果theme數據集位於名為music的資源目錄中,如果對該目錄勾選上了Provides Namespace in the Attributes inspector選項,那麼就可以通過名字"music/theme"來訪問該數據集了。

這樣,組織就可以通過資源目錄約定來使用它們了,而不會因為資源文件搞亂項目導航器與應用包的頂層結構。

在Xcode 7中,應用中的資源可以存儲在Apple的服務器上而不必添加到用戶從App Store所下載的應用包當中了。代碼隨後可以在後台下載用戶所需的任何資源,並且在不需要時還可以清除。請訪問Apple的On-Demand Resources Guide瞭解更多信息。

6.5.5 代碼文件與應用啟動過程

構建過程知道要編譯什麼代碼文件以形成應用的二進制文件,這是因為它們都在應用目標的Compile Sources構建階段中。對於Empty Window項目來說,它們是ViewController.swift與AppDelegate.swift。隨著應用開發的進行,你可能會不斷向項目添加代碼文件,這時要確保它們是目標的一部分,並且位於Compile Sources構建階段中。經常出現的情況是你想向代碼中添加新的對象類型聲明;這通常是通過向項目添加新文件來實現的,因為這會使得對像類型聲明很容易就能找到,而且Swift的私有性依賴於將代碼隔離到不同的文件中(參見第5章)。

在通過File→New→File新建文件時,你可以指定Cocoa Touch Class模板或Swift File模板。Swift File模板只不過是個空白文件而已:它僅僅導入了Foundation框架而已。如果你希望繼承某個Cocoa類,那麼Cocoa Touch Class模板通常就更為適合了;Xcode會幫你生成初始的類聲明,對於某些常見的父類繼承來說,如UIViewController與UITableViewController,它甚至提供了一些類方法的樁聲明。

當應用啟動時,系統知道在應用包的何處尋找二進制文件,因為應用包的Info.plist文件有個「Executable file」鍵(CFBundleExecutable),其值是二進制文件的文件名;在默認情況下,二進制文件名來自於EXECUTABLE_NAME環境變量(如「Empty Window」)。

1.入口點

應用啟動過程中最為複雜的部分就是開始。找到並加載了二進制後,系統必須要調用它,但在哪裡調用呢?如果應用是個Objective-C程序,那麼答案就是顯而易見的。Objective-C是C,因此入口點就是main函數。我們的項目有個main.m文件,它包含了main函數,如以下代碼所示:


int main(int argc, char *argv) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil,
            NSStringFromClass([AppDelegate class]));
    }
}
  

main函數只做了兩件事:

·創建了內存管理環境:@autoreleasepool與後面的花括號。

·它會調用UIApplicationMain函數,該函數做了很多事情,它會讓應用啟動並運行。

不過,我們的應用是個Swift程序,它沒有main函數!相反,Swift有個特殊的特性:@UIApplicationMain。查看AppDelegate.swift文件,你會看到這個特性,它位於AppDelegate類的聲明之上:


@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  

該特性本質上完成了Objective-C main.m文件所做的全部事情:它會創建一個入口點並調用UIApplicationMain來啟動應用。

在某些情況下,你可能想要移除@UIApplicationMain特性並替換為一個main文件,沒問題。文件可以是Objective-C文件或Swift文件。假設是個Swift文件。你可以創建一個main.swift文件並確保將其添加到應用委託中。其名字很重要,因為名為main.swift的文件會得到特殊對待:可以將可執行代碼放到文件的頂層。文件中應該包含與Objective-C對UIApplicationMain的調用相當的代碼:


import UIKit
UIApplicationMain(
    Process.argc, Process.unsafeArgv, nil, NSStringFromClass(AppDelegate))
  

為什麼要這麼做呢?大概是因為你想要在main.swift文件中做些其他事情,或想要定制對UIApplicationMain的調用。

2.UIApplicationMain

無論是編寫自己的main.swift文件還是使用Swift@UIApplicationMain特性都會調用UIApplicationMain。這個函數調用是應用要做的一件重要事情。整個應用除了調用UIApplicationMain,就沒別的了!此外,UIApplicationMain負責解決應用運行時會遇到的一些棘手問題。應用在何處創建最初的實例呢?一開始會調用這些實例上的哪些實例方法呢?應用的初始界面來自於何處?下面來看看UIApplicationMain到底做了哪些事情:

1.UIApplicationMain會創建應用的首個實例,即共享的應用實例,隨後可以通過調用UIApplication.sharedApplication()來訪問該實例。對UIApplicationMain調用的第3個參數(一個字符串)指定了該共享應用實例是哪個類的實例。如果該參數為nil(通常情況下均如此),那麼默認類就是UIApplication。如果不為nil,那就需要繼承UIApplication,這時需要將子類替換為一個顯式的值,比如,NSStringFromClass(MyApplicationSubclass,取決於調用的子類),並將其作為第3個參數來調用UIApplicationMain。

2.UIApplicationMain還會創建應用的第2個實例,即應用實例的委託。委託是一種重要且應用廣泛的Cocoa模式,第11章將會對其進行詳細介紹。它非常重要,你所編寫的每個應用都會有一個應用委託實例。對UIApplicationMain調用的第4個參數(是個字符串)指定了應用委託實例是什麼類。在手工版本的main.swift中,它就是NSStringFromClass(AppDelegate)。如果使用@UIApplicationMain特性,那麼在默認情況下,該特性會被放到AppDelegate.swift中的AppDelegate類聲明之上;該特性表示:「這是應用委託類。」

3.如果Info.plist文件指定了主故事板文件,那麼UIApplicationMain就會加載它並尋找故事板的初始視圖控制器(或是故事板的入口點);它會實例化該視圖控制器,從而創建應用的第3個實例。對於我們的Empty Window項目,它由Single View Application模板創建,該視圖控制器是名為ViewController的類實例;定義了該類以及ViewController.swift的代碼文件也是由該模板創建的。

4.如果有主故事板文件,那麼UIApplicationMain現在就會創建應用的窗口,這是應用的第4個實例,即UIWindow的實例(或者,應用委託可以替換UIWindow子類的實例)。它會將該窗口實例作為應用委託的window屬性;它還會將初始的視圖控制器實例作為窗口實例的rootViewController屬性。該視圖現在是應用的根視圖控制器。

5.UIApplicationMain現在轉向了應用委託實例並開始調用它的一些代碼,如application:didFinishLaunchingWithOptions:。自定義代碼可以借此機會運行!application:didFinishLaunchingWithOptions:就是放置用於初始化值以及執行啟動任務的代碼的絕佳之處;不過,請不要將耗費時間的代碼放置在這裡,因為這時應用的界面尚未出現。

6.如果有主故事板,那麼UIApplicationMain現在就可以讓應用界面出現了。這是通過調用UIWindow的實例方法makeKeyAndVisible實現的。

7.現在窗口要出現了。這反過來又會導致窗口轉向根視圖控制器,並告訴它獲取其主視圖,它會出現並佔據窗口。如果視圖控制器從.storyboard或.xib文件獲取視圖,那麼相應的nib文件就會加載進來;其對像會被實例化和初始化,它們會成為初始界面的對象:視圖會被放到窗口中,它與子視圖會對用戶可見。視圖控制器的viewDidLoad這時也會被調用——這是你的代碼提前開始運行的另一個時機。

應用現在就會啟動並運行!它有一組初始實例,至少有共享應用實例、窗口、初始視圖控制器與初始視圖控制器的視圖,以及它所包含的界面對象。你的一些代碼已經開始運行:UIApplicationMain依舊在運行(它永遠不會返回),就在那兒,注視著用戶的一舉一動,維護著事件循環,而事件循環會在用戶動作發生時對其做出響應。

3.沒有故事板的應用

在對應用啟動過程的描述中,我使用幾次短語「如果有主故事板」。在Xcode 7應用模板中,比如,用於生成Empty Window項目的Single View Application模板,有一個主故事板。不過,也可以沒有主故事板。在這種情況下,如創建窗口實例、為其賦予根視圖控制器、將其賦給應用委託的window屬性,以及調用窗口的makeKeyAndVisible來顯示界面等,都需要通過代碼來實現。

為了說明這一點,請通過Single View Application模板新建一個iPhone項目,叫作Truly Empty。然後按照如下步驟進行:

1.編輯目標。在General窗格中,選擇Main Interface域中的「Main」,然後將其刪除(並按下Tab鍵使之生效)。

2.從項目中刪除Main.storyboard與ViewController.swift。

3.選中並刪除AppDelegate.swift中的所有代碼。

現在的項目有一個應用委託,但卻沒有故事板,沒有代碼!為了創建一個最小可運行的應用,你需要按照這種方式編輯AppDelegate.swift來重新創建AppDelegate類,只保留創建和顯示窗口的代碼,如示例6-1所示。

示例6-1:沒有故事板的應用委託類


import UIKit
@UIApplicationMain
class AppDelegate : UIResponder, UIApplicationDelegate {
    var window : UIWindow?
    func application(application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?)
        -> Bool {
            self.window = UIWindow
            self.window!.rootViewController = UIViewController
            self.window!.backgroundColor = UIColor.whiteColor
            self.window!.makeKeyAndVisible
            return true
    }
}
  

這會生成一個可運行的最小應用,它有一個空白窗口;可以通過將窗口的backgroundColor改為其他顏色(如UIColor.redColor())並再次運行應用來驗證創建窗口的代碼的正確性。

這是個可運行的應用,不過卻沒什麼用。它什麼都沒做,也做不了,因為其根視圖控制器是通用的UIViewController。我們這裡需要的是自己的視圖控制器實例(包含自己的代碼),可以在nib中配置的視圖。下面來創建一個UIViewController子類以及包含其視圖的.xib文件:

1.選擇File→New→File。在「Choose a template」對話框中,iOS下面,單擊左側的Source,然後選擇Cocoa Touch Class。單擊Next。

2.將類命名為MyViewController,指定它為UIViewController的子類。勾選「Also create XIB file」復選框。指定語言為Swift。單擊Next。

3.這時會彈出Save對話框。請確保將文件保存到Truly Empty目錄中、將Group彈出菜單設為Truly Empty,並勾選Truly Empty目標,這些文件會成為應用目標的組成部分。單擊Create。

Xcode已經為我們創建了兩個文件:MyViewController.swift(將MyViewController作為UIViewController的子類)與MyViewController.xib(nib的源,MyViewController實例會在這裡獲得其視圖)。

4.在AppDelegate.swift中,回到應用委託的application:didFinishLaunchingWithOptions:,將根視圖控制器的類修改為MyViewController,並將其關聯到nib,如以下代碼所示:


self.window!.rootViewController =
    MyViewController(nibName:"MyViewController", bundle:nil)
  

我們現在沒有使用故事板創建了一個可用且最小的應用項目。代碼完成了存在主故事板的情況下UIApplicationMain所自動完成的一些工作:我們實例化了UIWindow,將窗口實例設為了應用委託的window屬性;實例化了一個初始視圖控制器,讓窗口能夠出現。此外,窗口的出現會自動導致MyViewController實例從MyViewController.xib編譯好的nib中獲取到其視圖;這樣,我們可以通過MyViewController.xib來自定義應用的初始界面。除了說明UIApplicationMain隱式所做的事情,這也是一種構建應用的合理方式。

6.5.6 框架與SDK

框架就是你的代碼所用的編譯好的代碼庫。在進行iOS編程時,你所用的大多數框架都是Apple內建的框架。這些框架已經成為應用運行的設備系統的一部分了,位於/System/Library/Frameworks下,但在iPhone或iPad上就沒法指定了,因為我們沒法(通常情況下如此)直接查看文件的層次結構。

在構建項目並在電腦上運行時,編譯好的代碼還需要連接到這些框架上。為了達成所願,iOS設備的System/Library/Frameworks在電腦上會有個副本,就在Xcode中。這種設備系統的副本子集稱作SDK(即「軟件開發包」)。到底使用哪個SDK取決於構建目標是什麼。

鏈接指的是將編譯好的代碼與所需框架連接起來的過程,這些框架在構建期位於一個地方,但在運行期卻在另一個地方。比如:

當構建代碼以在設備上運行時

會使用所需框架的副本。該副本位於iPhone SDK的System/Library/Frameworks中,它在Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk中。

當代碼在設備上運行時

代碼開始運行時會在設備頂層目錄/System/Library/Frameworks中查找所需框架。

通過框架這種方式,當應用運行時,Apple的代碼就會動態合併到你的應用中。框架是每個應用所需的東西的集散地;它們就是Cocoa。內容有很多,編譯好的代碼也有很多。應用會共享框架的精華與功能,因為應用會鏈接到框架上。代碼運行時就好像框架代碼是其一部分一樣。不過,應用只會完成很少的一部分工作;框架才是真正的能量之源。

鏈接會負責將編譯好的代碼連接到所需的框架上,不過這還不足以讓代碼編譯通過。框架中有很多你的代碼會調用的類(如NSString)與方法(如rangeOfString:)。為了滿足編譯器的要求,框架會在其頭文件中發佈API,代碼則會導入框架頭文件。比如,代碼可以使用NSString並調用rangeOfString:,這是因為它導入了NSString頭文件。事實上,你的代碼所導入的是UIKit頭文件,它反過來又導入了Foundation頭文件,而Foundation又導入了NSString頭文件。可以在自己代碼的頭文件中看到這一點:


import UIKit  

按住Command鍵並單擊UIKit會轉到Swift的UIKit頭文件中。文件頂部是import Foundation。查看Foundation頭文件並向下滾動,你會看到import Foundation.NSString。查看NSString頭文件,你會看到rangeOfString:方法的聲明。

這樣,使用框架需要兩步:

導入框架的頭文件

代碼需要這個信息才能成功編譯。代碼會通過import關鍵字導入框架的頭文件,從而導入框架,或導入的框架再導入所需的框架。在Swift中,可以通過模塊名指定框架。

鏈接到框架

運行時,編譯後的可執行二進制文件需要連接到所用的框架上,這會將編譯後的代碼與這些框架合併起來。代碼構建完畢後,它會鏈接到所需的框架上,按照目標Link Binary With Libraries構建階段所列出的框架進行。

不過,我們的項目並沒有任何顯式的鏈接。查看應用目標的Link Binary With Libraries構建階段,你會發現它是空的。這是因為Swift使用了模塊,模塊可以進行自動鏈接。在Objective-C中,這兩個特性都是可選的,並且由構建設置管理。不過在Swift中,模塊與自動鏈接的使用則是自動的。

模塊是存儲在電腦Library/Developer/Xcode/DerivedData/ModuleCache中的緩存信息。僅僅打開一個Swift項目就會使得任何被導入的模塊都緩存在那裡。進入ModuleCache目錄中,你會看到大量框架與頭文件(.pcm文件)所構成的模塊。Swift對模塊的使用簡化了導入與鏈接的過程,並加快了編譯時間。

模塊是精巧且便捷的,不過有時還需要手工鏈接到框架上。比如,假設你想在界面中使用MKMapView(Map Kit View)。你可以在.storyboard或.xib文件中配置,不過當構建和運行應用時,應用會崩潰,消息是「Could not instantiate class named MKMapView.」。原因在於nib在加載時會發現它包含了一個MKMapView,但卻不知道MKMapView是什麼。MKMapView定義在MapKit框架中,但nib並不知道這一點。

在代碼頂部加上import MapKit也無法解決這個問題;如果代碼想要使用MKMapView,你就需要這麼做,不過這卻沒辦法在nib加載時讓其知道何為MKMapView。解決辦法就是手工鏈接到MapKit框架:

1.編輯目標,查看構建階段窗格。

2.在Link Binary With Libraries下單擊+按鈕。

3.這時會出現一個可用框架列表(還有一些動態庫)。向下滾動到MapKit.framework,將其選中並單擊Add。

這可以解決問題:應用現在可以構建並運行了。

你還可以創建自己的框架並作為項目的一部分。框架是個模塊,因此也有助於結構化你的代碼,正如第5章介紹Swift隱私性時所述。要想創建新的框架:

1.編輯目標並選擇Editor→Add Target。

2.在對話框左側,iOS下,選擇Framework & Library;在右側,選擇Cocoa Touch Framework,單擊Next。

3.為框架取個名字;叫它Coolness。你可以選擇語言,不過我不確定這麼做是否有用,因為現在還沒有創建任何代碼文件。默認情況下,應用彈出菜單中的Project and Embed應該已經被正確設定好了,單擊Finish。

這時,項目中會創建好一個新的Coolness框架目標。如果向Coolness目標添加一個.swift文件,並在裡面定義一個對像類型,將其聲明為public;回到一個主應用目標文件上,如AppDelegate.swift,那麼代碼就可以import Coolness,並且能夠看到該對像類型及裡面的公共成員了。