建立一個靜态庫工程
打開Xcode,點選File\New\Project,選擇iOS\Framework and Library\Cocoa Touch Static Library建立一個靜态庫工程.
将工程命名為RWUIControls(你的架構名, 下同),然後将工程儲存到一個空目錄下。
一個靜态庫工程由頭檔案和實作檔案組成,這些檔案将被編譯為庫本身。
為了友善其他開發者使用你的庫和framework,你将進行一些操作,讓他們僅需要導入一個頭檔案便可以通路所有你想公開的類。
當建立靜态庫工程時,Xcode會自動添加RWUIControls.h和RWUIControls.m。你不需要實作檔案,是以右鍵單擊RWUIControls.m選擇delete,将它删除到廢紙簍中。
打開RWUIControls.h,将所有内容替換為:
1 | |
導入UIKit的頭檔案,這是建立一個庫所需要的。當你在建立不同的組成類時,你将會将它們添加到這個檔案中,確定它們能夠被庫的使用者擷取到。
你所建構的項目依賴于UIKit,然而Xcode的靜态庫工程不會自動連接配接到UIKit。要解決這個問題,就要将UIKit作為依賴庫添加到工程中。在工程導航欄中選擇工程名,然後在中央面闆中選擇RWUIControls目标。
點選BuildPhases,展開Link Binary with Libraries這一部分,點選+添加一個新的framework,找到UIKit.framework,點選add添加進來。
如果不結合頭檔案,靜态庫是沒有用的,靜态庫編譯一組檔案,在這些檔案中類和方法都以二進制資料的形式存在。在你建立的庫中,有些類将能夠被公開通路到,有些類隻能由庫内部通路并使用。
接下來,你需要在build欄中添加新的phase,來包含所有頭檔案,并将它們放到編譯器可以擷取到的某個地方。然後,你将會拷貝這些到你的framework中。
依然是在Xcode的Build Phases界面,選擇Editor\Add Build Phase\Add Copy Headers Build Phase。
Note:如果你發現按上面找到的菜單項是灰色的(不可點選的),點選下方Build Phases界面的白色區域來擷取Xcode的應用焦點,然後重新試一下。
把RWUIControls.h從項目導航欄中拖到中央面闆的Copy Headers下的Public部分。這一步確定任何使用你的庫的使用者均可以擷取該頭檔案。
Note:顯然,所有包含在你的公共頭檔案中的頭檔案必須是對外公開的,這一點非常重要。否則,開發者在使用你的庫時會得到編譯錯誤。如果Xcode在讀取公共頭檔案時不能讀到你忘記設為public的頭檔案,這實在是太令人沮喪了。
把項目中要用到的API接口檔案,從Finder中拖到Xcode下RWUIControls目錄下。
選擇Copy items into destination group’s folder,點選下方的選擇框,確定RWUIControls靜态庫目标被選中。
這一步預設把實作檔案添加到編譯清單,把頭檔案添加到Project組。這意味着它們目前是私有的。
Note:在你弄清楚之前,這三個組的名稱可能會讓你迷惑,Public是你期望的,Private下的頭檔案依然是可以暴露出來的,是以名字可能有些誤導。諷刺的是,在Project下的頭檔案對你的工程來說才是“私有”的,是以,你将會更多地希望你的頭檔案或者在Public下,或者在Project下。
現在,你需要将控件的頭檔案RWKnobControl.h分享出來,有幾種方式可以實作這一點,首先是在Copy Headers面闆中将這個頭檔案從Project欄拖到Public欄。
或者,你可能會發現,更簡單的方法是,編輯檔案,改變Target Membership面闆下的membership。這個選項更友善一些,可以讓你不斷添加檔案,擴充你的庫。
Note:如果你不斷往庫中添加新的類,記得及時更新這些類的關系(membership),使盡可能少的類成為public,并確定其他非public的頭檔案都在Project下。
對你的控件的頭檔案需要做的另一件事是将其添加到庫的主頭檔案RWControls.h中。在這個主頭檔案的幫助下,開發者使用你的庫僅僅需要導入一個頭檔案,如下面的代碼一樣,而不是自己去選擇自己需要的一塊導入。
1 | |
是以,在RWUIControls.h中添加下面的代碼:
1 2 | |
配置Build Settings
現在距離建構這個項目、建立靜态庫已經非常接近了。不過,這裡要先進行一些配置,讓我們的庫對于使用者來說更友好。
首先,你需要提供一個目錄名,表示你将把拷貝的公共頭檔案存放到哪裡。這樣確定當你使用靜态庫的時候可以定位到相關頭檔案的位置。
在項目導航欄中點選項目名,然後選擇RWUIControls靜态庫目标,選擇Build Setting欄,然後搜尋public header,輕按兩下Public Headers Folder Path,在彈出視圖中鍵入如圖所示内容:
一會你就會看到這個目錄了。
現在你需要改變一些其他的設定,尤其是那些在二進制庫中遺留下的設定,編譯器提供給你一個選項,來消除無效代碼:永遠不會被執行的代碼。當然你也可以移除掉一些debug用符号,例如某些函數名稱或者其他跟debug相關的細節。
因為你正在建立framework供他人使用,最好禁掉這些功能(無效代碼和debug用符号),讓使用者自己選擇對自己的項目有利的部分使用。和之前一樣,使用搜尋框,改變下述設定:
- Dead Code Stripping設定為NO
- Strip Debug Symbol During Copy 全部設定為NO
- Strip Style設定為Non-Global Symbols
編譯然後運作,到目前為止沒什麼可看的,不過確定項目可以成功建構,沒有錯誤和警報是非常好的。
選擇目标為iOS Device,按下command + B進行編譯,一旦成功,工程導航欄中Product目錄下libRWUIControls.a檔案将從紅色變為黑色,表明現在該檔案已經存在了。右鍵單擊libRWUIControls.a,選擇Show in Finder。
再此目錄下,你将看到靜态庫,libRWUIControls.a,以及其他你為頭檔案指定的目錄。注意到,正如你所期望的,那些定為public的頭檔案可以在此看到。
建立一個Framework
到現在,你可能迫不及待地點着腳趾頭,想着什麼時候framework可以出來。可以了解,因為到現在為止你已經做了許多工作,然而卻沒有看到過framework的身影。
現在該有所改變了,你之是以到現在都沒有建立一個framework,是因為framework本身就是靜态庫加上一組頭檔案——實際上正是你已經建立好的東西。
當然,framework也有幾點不同之處:
- 目錄結構。Framework有一個能被Xcode識别的特殊的目錄結構,你将會建立一個build task,由它來為你建立這種結構。
- 片段(Slice)。目前為止,當你建構庫時,僅僅考慮到目前需要的結構(architecture)。例如,i386、arm7等,為了讓一個framework更有用,對于每一個運作framework的結構,該framework都需要建構這種結構。一會你就會建立一個新的工程,建構所有需要的結構,并将它們包含到framework中。
這一部分非常神奇,不過我們會慢慢地來。實際上它并不像看起來那樣複雜。
Framework結構
選擇Build Phases欄,然後選擇Editor/Add Build Phase/Add Run Script Build Phase來添加一個新的腳本。
這一步在build phases部分添加了一個新的面闆,這允許你在建構時運作一個Bash腳本。你希望讓腳本在build的過程中何時執行,就把這個面闆拖動到清單中相對應的那一位置。對于該framework工程來說,腳本最後執行,是以你可以讓它保留在預設的位置即可。
輕按兩下面闆标題欄Run Script,重命名為Build Framework。
在腳本文本框中粘貼下面的Bash腳本代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
現在,選擇RWUIControls靜态庫scheme,然後選擇iOS Device建構目标,然後使用cmd+B建構。
在RWUIControls工程裡Products目錄下右鍵單擊libRWUIControls.a靜态庫,然後再一次選擇Show in Finder。
在這次建構目錄中你可以看到RWUIControls.framework,可以确定一下這裡展示了正确的目錄結構:
這算是在完成你的framework的過程中邁出了一大步。不過你會注意到這裡并沒有一個靜态lib檔案。這就是我們下一步将要解決的問題。
多架構(Multi-Architecture)編譯
iOS app需要在許多不同的CPU架構下運作:
- arm7: 在最老的支援iOS7的裝置上使用
- arm7s: 在iPhone5和5C上使用
- arm64: 運作于iPhone5S的64位 ARM 處理器 上
- i386: 32位模拟器上使用
- x86_64: 64為模拟器上使用
每個CPU架構都需要不同的二進制資料,當你編譯一個應用時,無論你目前正在使用那種架構,Xcode都會正确地依照對應的架構編譯。例如,如果你想跑在虛拟機上,Xcode隻會編譯i386版本(或者是64位機的x86_64版本)。
這意味着編譯會盡可能快地進行,當你歸檔一款app或者建構app的釋出版本(release mode)時,Xcode會建構上述三個用于真機的ARM架構。是以這樣app就可以跑在所有裝置上了。不過,其他的編譯架構又如何呢?
當你建立你的framework時,你自然會想讓所有開發者都能在所有可能的架構上運作它,不是嗎?你當然想,因為這樣可以從同行那兒得到尊敬與贊美。
是以你需要讓Xcode在所有架構下都進行編譯。這一過程實際上是建立了二進制FAT(File Allocation Table,檔案配置表),它包含了所有架構的片段(slice)。
Note:這裡實際上強調了建立依賴靜态庫的示例項目的另一個原因:庫僅僅在示例項目運作所需要的架構下編譯,隻有當有變化的時候才重新編譯,為什麼這一點會讓人激動?因為開發周期會盡可能地縮短。
這裡将使用在RWUIControls工程中的一個新的目标來建構framework,在項目導航欄中選擇RWUIControls,然後點選已經存在的目标下面的Add Target按鈕(或editor下面Add Target)。
找到iOS/Other/Aggregate,點選Next,将目标命名為Framework。
Note:為什麼使用集合(Aggregate)目标來建立一個framework呢?為什麼這麼不直接?因為OS X對庫的支援更好一些,事實上,Xcode直接為每一個OS X工程提供一個Cocoa Framework編譯目标。基于此,你将使用集合編譯目标,作為Bash腳本的連接配接串來建立神奇的framework目錄結構。你是不是開始覺得這裡的方法有些愚蠢了?
為了確定每當這個新的framework目标被建立時,靜态連結庫都會被編譯,你需要往靜态庫目标中添加依賴(Dependency)。在庫工程中選擇Framework目标,在Build Phases中添加一個依賴。展開Target Dependencies面闆,點選 + 按鈕選擇RWUIControls靜态庫。
這個目标的主要編譯部分是多平台編譯,你将使用一個腳本來做到這一點。和你之前做的一樣,在Framework目标下,選擇Build Phases欄,點選Editor/Add Build Phase/Add Run Script Build Phase,建立一個新的Run Script Build Phase。
輕按兩下Run Script,重命名腳本的名字。這次命名為MultiPlatform Build。
在腳本文本框中粘貼下面的Bash腳本代碼:
1 2 3 4 5 6 7 8 9 10 11 | |
把下面的代碼加到腳本的底部。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
把下面的代碼添加到腳本的底部。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
在腳本最後添加下面的代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
腳本的最後是簡單的拷貝指令,将下面代碼添加到腳本最後:
1 2 3 4 5 6 | |
選擇Framework集合方案(aggregate scheme),按下cmd+B編譯該framework。
這一步将建構并在你的桌面上存放一個RWUIControls.framework。
為了檢查一下我們的多平台編譯真的成功了,啟動終端,導航到桌面上的framework,像下面一樣:
1 2 | |
第一條指令導航到framework中,第二行使用lipo指令從RWUIControls靜态庫中得到需要的資訊,這将列出存在于該庫中的所有片段。
這裡你可以看到,一共有五種片段:i386, x86_64, arm7, arm7s 和 arm64,正如你在編譯時設定的那樣。如果你之前使用lipo –info指令,你可以看到這些片段的一個分組。