iOS 靜態庫製作,Framework製作,Bundle製作
在上一篇教程(中譯版)中,你學到了怎麼樣建立一個可複用的圓形旋鈕控制元件。然而你可能不清楚怎樣讓其他開發者更方便地去複用它。
如果你想將你開發的控制元件與別人分享,一種方法是直接提供原始碼檔案。然而,這種方法並不是很優雅。它會暴露所有的實現細節,而這些實現你可能並不想開源出來。此外,開發者也可能並不想看到你的所有程式碼,因為他們可能僅僅希望將你的這份漂亮程式碼的一部分植入自己的應用中。
另一種方法是將你的程式碼編譯成靜態庫(library),讓其他開發者新增到自己的專案中。然而,這需要你一併公佈所有的公開的標頭檔案,實在是非常不方便。
你需要一種簡單的方法來編譯你的程式碼,這種方法應該使得你的程式碼易分享,並且在多個工程中易複用。你需要的是一種方法來打包你的靜態庫,將所有的標頭檔案放到一個單元中,這樣你就可以立刻將其加入到你的專案中並使用。
非常幸運,這正是本篇教程所要解決的問題。你將會學到製作並使用Framework,幫助你解決這個頭疼的問題。OS X完美地支援這一點,因為Xcode就提供了一個專案模板,包含著預設構建目標(target)和可以容納類似於圖片、聲音、字型等資源的檔案。你可以為iOS建立Framework,不過這是一個比較複雜的手工活,如果你跟著教程走,你將學到怎麼樣跨過路障,順利地完成Framework的建立。
當你跟著這篇教程走完後,你將能夠:
-
使用Xcode構建一個基本的靜態庫工程。
-
依賴於該靜態庫工程構建一款應用。
-
掌握如何將靜態庫工程轉換為完整的、合格的Framework。
-
最終,你將看到如何將一個影象檔案同Framework一起打包到resource bundle下。
開始
這篇教程的主要目的是解釋怎麼樣在你的iOS工程中建立並使用一個Framework。所以,不像其他網站上的教程,這篇教程將只使用一小部分Objective-C程式碼,並且這一小部分主要是為了說明我們將會遇到的一些概念。
從這裡下載可用的資原始檔RWKnobControl。如果你在Creating a Static Library Project 這篇文章中完成了建立第一個專案的過程,這裡你將會看到怎麼樣使用去它們。
在建立本工程時,你將要建立的所有的程式碼和專案檔案都可以在Github上找到。對於本篇教程中每個建立階段都有不同的commit。
什麼是Framework?
Framework是資源的集合,將靜態庫和其標頭檔案包含到一個結構中,讓Xcode可以方便地把它納入到你的專案中。
在OS X上,可能會建立一個動態連線(Dynamically Linked)的framework。通過動態連線,framework可以更新,不需要應用重新連線。在執行時,庫中程式碼的一份拷貝被分享出來,整個工程都可以使用它,因此,這樣減少了記憶體消耗,提高了系統的效能。正如你看到的,這是一個功能強大的特性。
在iOS上,你不能用這種方式新增為系統新增自定義的framework,因此僅有的動態連結的framework只能是Apple提供的那些。(編者注:在iOS 8中已加入此特性,開發者可以使用第三方的動態框架)
然而,這並不意味著framework對於iOS而言是無關緊要的,靜態連線的framework依然可以打包程式碼,使其在不同的應用中複用。
由於framework本質上是靜態庫的“一站式採購點”,因此在本篇教程中你所做的第一件事就是建立並使用靜態庫。當跟著教程走到如何建立framework時,你就能明白你所做的一切了,整體思路也不會那麼煙霧繚繞了。
建立一個靜態庫工程
開啟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 |
#import < UIKit/UIKit.h>
|
匯入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的標頭檔案,這實在是太令人沮喪了。
建立一個UI控制元件
既然你已經設定好你的工程了,是時候為你的庫新增一些功能了。由於本篇教程的關鍵在於教你怎麼樣建立一個framework,而不是怎麼樣構建一個UI控制元件,這裡你將使用上一篇教程中建立好的控制元件。在你之前下載好的壓縮包檔案中找到RWKnobControl目錄,從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 |
#import < RWUIControls/RWUIControls.h>
|
因此,在RWUIControls.h中新增下面的程式碼:
1 2 |
// Knob Control
#import
|
配置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的標頭檔案可以在此看到。
建立一個依賴開發(Dependent Development)工程
在無法看到真實效果的情況下為iOS開發一個UI控制元件庫是極其困難的,而這是我們現在面臨的問題。
沒有人期望你閉著眼睛開發出一個UI控制元件,因此在這一部分你將建立一個新的Xcode工程,該工程依賴於你剛剛建立好的庫。這意味著允許你使用示例app建立一個framework。當然,這部分程式碼將和庫本身完全分離,結構會非常清晰。
選擇File\Close Project關閉之前的靜態庫工程,使用File\New\Project建立一個新的工程,選擇iOS\Application\Single View Application,將新工程命名為UIControlDevApp,將類字首命名為RW,選擇該工程只支援iPhone,最後將專案儲存到和之前的RWUIControls相同的目錄下。
新增RWUIControls依賴庫,將RWUIControls.xcodeproj從Finder中拖到Xcode中UIControlDevApp組下。
現在你可以在你的工程中導航到庫工程了,這樣做非常好,因為這樣意味著你可以在庫中編輯程式碼,並且執行示例工程來測試你做的改變。
Note:你無法將同一工程在兩個Xcode視窗中同時開啟,如果你發現你無法在你的工程中導航到庫工程的話,檢查一下是否庫工程在其他Xcode視窗中打開了。
這裡你可以拷貝程式碼,而不是和上一個教程似的重新建立程式碼。首先,選擇Main.storyboard, RWViewController.h 和 RWViewController.m,然後右鍵單擊,選擇Delete,將它們刪除到廢紙簍中。然後,將你之前下載的壓縮檔案中DevApp資料夾拷貝到Xcode的UIControlDevApp組下。
現在,你將新增靜態庫作為例項專案的依賴庫:
-
在專案導航欄中選擇UIControlDevApp。
-
導航到UIControlDevApp目標下Build Phases面板下。
-
開啟Target Dependencies面板,點選+按鈕調出選擇器。
-
找到RWUIControls靜態庫,選擇並點選Add。這一步表明當構建dev應用時,Xcode會檢查是否靜態庫需要重新構建。
為了連線到靜態庫本身,展開Link Binary With Libraries面板,再次點選+按鈕,從Workspace組中選擇libRWUIControls.a然後點選Add。
這一步確保Xcode可以連線到靜態庫,就像連線到系統framework(例如UIKit)一樣。
編譯並執行,如果你按照之前的教程建立了一個旋鈕控制元件,在你眼前展示的將是與之相同的應用。
像這樣使用巢狀工程的好處是你可以對庫本身做出修改,而不用離開示例工程,即使你同時改變兩個地方的程式碼也一樣。每次你編譯工程,你都要檢查是否將標頭檔案的public/project關係設定正確。如果例項工程中缺失了任何需要的標頭檔案,它都不能被編譯。
建立一個Framework
到現在,你可能迫不及待地點著腳趾頭,想著什麼時候framework可以出來。可以理解,因為到現在為止你已經做了許多工作,然而卻沒有看到過framework的身影。
現在該有所改變了,你之所以到現在都沒有建立一個framework,是因為framework本身就是靜態庫加上一組標頭檔案——實際上正是你已經建立好的東西。
當然,framework也有幾點不同之處:
-
目錄結構。Framework有一個能被Xcode識別的特殊的目錄結構,你將會建立一個build task,由它來為你建立這種結構。
-
片段(Slice)。目前為止,當你構建庫時,僅僅考慮到當前需要的結構(architecture)。例如,i386、arm7等,為了讓一個framework更有用,對於每一個執行framework的結構,該framework都需要構建這種結構。一會你就會建立一個新的工程,構建所有需要的結構,並將它們包含到framework中。
這一部分非常神奇,不過我們會慢慢地來。實際上它並不像看起來那樣複雜。
Framework結構
正如之前提到的,一個framework有一個特殊的目錄結構,看起來像是這樣的:
現在你需要在靜態庫構建過程中新增指令碼來建立這種結構,在專案導航欄中選擇RWUIControls,然後選擇RWUIControls靜態庫目標,選擇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 |
set -e
export FRAMEWORK_LOCN= "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework"
# Create the path to the real Headers die
mkdir -p "${FRAMEWORK_LOCN}/Versions/A/Headers"
# Create the required symlinks
/bin/ln -sfh A "${FRAMEWORK_LOCN}/Versions/Current"
/bin/ln -sfh Versions/Current/Headers "${FRAMEWORK_LOCN}/Headers"
/bin/ln -sfh "Versions/Current/${PRODUCT_NAME}" \
"${FRAMEWORK_LOCN}/${PRODUCT_NAME}"
# Copy the public headers into the framework
/bin/cp -a "${TARGET_BUILD_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/" \
"${FRAMEWORK_LOCN}/Versions/A/Headers"
|
這個指令碼首先建立了RWUIControls.framework/Versions/A/Headers目錄,然後建立了一個framework所需要的三個連線符號(symbolic links)。
-
Versions/Current => A
-
Headers => Versions/Current/Headers
-
RWUIControls => Versions/Current/RWUIControls
最後,將公共標頭檔案從你之前定義的公共標頭檔案路徑拷貝到Versions/A/Headers目錄下,-a引數確保修飾次數作為拷貝的一部分不會改變,防止不必要的重新編譯。
現在,選擇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按鈕。
找到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 |
set -e
# If we're already inside this script then die
if [ -n "$RW_MULTIPLATFORM_BUILD_IN_PROGRESS" ]; then
exit 0
fi
export RW_MULTIPLATFORM_BUILD_IN_PROGRESS=1
RW_FRAMEWORK_NAME=${PROJECT_NAME}
RW_INPUT_STATIC_LIB= "lib${PROJECT_NAME}.a"
RW_FRAMEWORK_LOCATION= "${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework"
|
-
set –e確保指令碼的任何地方執行失敗,則整個指令碼都執行失敗。這樣可以避免讓你建立一個部分有效的framework。
-
接著,用RW_MULTIPLATFORM_BUILD_IN_PROGRESS變數決定是否迴圈呼叫指令碼,如果有該變數,則退出。
-
然後設定一些變數。該framework的名字與專案的名字一樣。也就是RWUIControls,另外,靜態lib的名字是libRWUIControls.a。
接下來,用指令碼設定一些函式,這些函式一會專案就會用到,把下面的程式碼加到指令碼的底部。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function build_static_library {
# Will rebuild the static library as specified
# build_static_library sdk
xcrun xcodebuild -project "${PROJECT_FILE_PATH}" \
-target "${TARGET_NAME}" \
-configuration "${CONFIGURATION}" \
-sdk "${1}" \
ONLY_ACTIVE_ARCH=NO \
BUILD_DIR= "${BUILD_DIR}" \
OBJROOT= "${OBJROOT}" \
BUILD_ROOT= "${BUILD_ROOT}" \
SYMROOT= "${SYMROOT}" $ACTION
}
function make_fat_library {
# Will smash 2 static libs together
# make_fat_library in1 in2 out
xcrun lipo -create "${1}" "${2}" -output "${3}"
}
|
-
build_static_library把SDK作為引數,例如iPhone7.0,然後建立靜態lib,大多數引數直接傳到當前的構建工作中來,不同的是設定ONLY_ACTIVE_ARCH來確保為當前SDK構建所有的結構。
-
make_fat_library使用lipo將兩個靜態庫合併為一個,其引數為兩個靜態庫和結果的輸出位置。從這裡瞭解更多關於lipo的知識。
為了使用這兩個方法,接下來指令碼將定義更多你要用到的變數,你需要知道其他SDK是什麼,例如,iphoneos7.0應該對應iphonesimulator7.0,反過來也一樣。你也需要找到該SDK對應的編譯目錄。
把下面的程式碼新增到指令碼的底部。
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 - Extract the platform (iphoneos/iphonesimulator) from the SDK name
if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]; then
RW_SDK_PLATFORM=${BASH_REMATCH[1]}
else
echo "Could not find platform name from SDK_NAME: $SDK_NAME"
exit 1
fi
# 2 - Extract the version from the SDK
if [[ "$SDK_NAME" =~ ([0-9]+.*$) ]]; then
RW_SDK_VERSION=${BASH_REMATCH[1]}
else
echo "Could not find sdk version from SDK_NAME: $SDK_NAME"
exit 1
fi
# 3 - Determine the other platform
if [ "$RW_SDK_PLATFORM" == "iphoneos" ]; then
RW_OTHER_PLATFORM=iphonesimulator
else
RW_OTHER_PLATFORM=iphoneos
fi
# 4 - Find the build directory
if [[ "$BUILT_PRODUCTS_DIR" =~ (.*)$RW_SDK_PLATFORM$ ]]; then
RW_OTHER_BUILT_PRODUCTS_DIR= "${BASH_REMATCH[1]}${RW_OTHER_PLATFORM}"
else
echo "Could not find other platform build directory."
exit 1
fi
|
上面四句宣告都非常相似,都是使用字串比較和正則表示式來確定RW_OTHER_PLATFORM和RW_OTHER_BUILT_PRODUCTS_DIR。
詳細解釋一下上面四句宣告:
-
SDK_NAME將指代iphoneos7.0和iphonesimulator6.1,這個正則表示式取出字串開頭不包含數字的那些字元,因此,其結果是iphoneos 或 iphonesimulator。
-
這個正則表示式取出SDK_NAME中表示版本用的數字,7.0或6.1等。
-
這裡用簡單的字串比較來將iphonesimulator 轉換為iphoneos,反過來也一樣。
-
從構建好的工程的目錄路徑的末尾找出平臺名稱,將其替換為其他平臺。這樣可以確保為其他平臺構建的目錄可以被找到。這是將兩個靜態庫合併的關鍵部分。
現在你可以啟動指令碼為其他平臺編譯,然後得到合併兩靜態庫的結果。
在指令碼最後新增下面的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# Build the other platform.
build_static_library "${RW_OTHER_PLATFORM}${RW_SDK_VERSION}"
# If we're currently building for iphonesimulator, then need to rebuild
# to ensure that we get both i386 and x86_64
if [ "$RW_SDK_PLATFORM" == "iphonesimulator" ]; then
build_static_library "${SDK_NAME}"
fi
# Join the 2 static libs into 1 and push into the .framework
make_fat_library "${BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \
"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}" \
"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}"
|
-
首先,呼叫你之前定義好的函式為其他平臺編譯
-
如果你現在正在為模擬器編譯,那麼Xcode會預設只在該系統對應的結構下編譯,例如i386 或 x86_64。為了在這兩個結構下都進行編譯,這裡呼叫了build_static_library,基於iphonesimulator SDK重新編譯,確保這兩個結構都進行了編譯。
-
最後呼叫make_fat_library將在當前編譯目錄下的靜態lib同在其他目錄下地lib合併,依次實現支援多結構的FAT靜態庫。這個被放到了framework中。
指令碼的最後是簡單的拷貝命令,將下面程式碼新增到指令碼最後:
1 2 3 4 5 6 |
# Ensure that the framework is present in both platform's build directories
cp -a "${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}" \
"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework/Versions/A/${RW_FRAMEWORK_NAME}"
# Copy the framework to the user's desktop
ditto "${RW_FRAMEWORK_LOCATION}" "${HOME}/Desktop/${RW_FRAMEWORK_NAME}.framework"
|
-
第一條命令確保framework在所有平臺的編譯目錄中都存在。
-
第二條將完成的framework拷貝到使用者的桌面上,這一步是可選的,但我發現這樣做可以很方便的存取framework。
選擇Framework集合方案(aggregate scheme),按下cmd+B編譯該framework。
這一步將構建並在你的桌面上存放一個RWUIControls.framework。
為了檢查一下我們的多平臺編譯真的成功了,啟動終端,導航到桌面上的framework,像下面一樣:
1 2 |
$ cd ~/Desktop/RWUIControls.framework
$ RWUIControls.framework xcrun lipo -info RWUIControls
|
第一條指令導航到framework中,第二行使用lipo指令從RWUIControls靜態庫中得到需要的資訊,這將列出存在於該庫中的所有片段。
這裡你可以看到,一共有五種片段:i386, x86_64, arm7, arm7s 和 arm64,正如你在編譯時設定的那樣。如果你之前使用lipo –info指令,你可以看到這些片段的一個分組。
如何使用Framework?
OK,你已經有了framework,你也有了庫。它們可以提供一種優雅的方法來解決你迄今為止還沒有遇到過的問題,但是做這些的意義是什麼呢?
使用framework的其中一個主要的優點是簡化使用,現在你將建立一個簡單的iOS應用,並使用你剛剛建立好的RWUIControls.framework。
使用Xcode建立一個新工程,選擇File/New/Project,然後選擇iOS/Application/Single View Application,將新工程命名為ImageViewer,設定為僅僅用於iPhone,將其儲存到與之前兩個工程同樣的目錄下。這個應用將展示一張圖片,允許使用者使用RWKnobControl旋轉圖片。
在你之前下載的壓縮檔案中找到ImageViewer目錄,這裡面只有一個圖片檔案,把這個圖片檔案sampleImage.jpg從Finder中拖到Xcode的ImageViewer組中。
選中Copy items into destination group’s folder,點選Finish完成匯入操作。
匯入一個framework的步驟幾乎相同,將RWUIControls.framework從桌面拖到Xcode中的Frameworks組下。同樣,確保選中了Copy items into destination group’s folder。
開啟RWViewController.m,將裡面的程式碼替換為下面的程式碼:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#import "RWViewController.h"
#import < RWUIControls/RWUIControls.h>
@interface RWViewController ()
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) RWKnobControl *rotationKnob;
@end
@implementation RWViewController
- (void)viewDidLoad
{
[ super viewDidLoad];
// Create UIImageView
CGRect frame = self.view.bounds;
frame.size.height *= 2/3.0;
self.imageView = [[UIImageView alloc] initWithFrame:CGRectInset(frame, 0, 20)];
self.imageView.image = [UIImage imageNamed:@ "sampleImage.jpg" ];
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
[self.view addSubview:self.imageView];
// Create RWKnobControl
frame.origin.y += frame.size.height;
frame.size.height /= 2;
frame.size.width = frame.size.height;
self.rotationKnob = [[RWKnobControl alloc] initWithFrame:CGRectInset(frame, 10, 10)];
CGPoint center = self.rotationKnob.center;
center.x = CGRectGetMidX(self.view.bounds);
self.rotationKnob.center = center;
[self.view addSubview:self.rotationKnob];
// Set up config on RWKnobControl
self.rotationKnob.minimumValue = -M_PI_4;
self.rotationKnob.maximumValue = M_PI_4;
[self.rotationKnob addTarget:self
action:@selector(rotationAngleChanged:)
forControlEvents:UIControlEventValueChanged];
}
- (void)rotationAngleChanged:(id)sender
{
self.imageView.transform = CGAffineTransformMakeRotation(self.rotationKnob.value);
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
@end
|
這就是一個簡單的檢視控制器,它做了以下幾件事:
-
使用#import匯入框架的標頭檔案
-
設定了一組私有屬性來持有UIImageView和RWKnobControl。
-
建立一個UIImageView,將其放到合適的位置。
-
為Knob control設定一些屬性,包括新增值改變的事件監聽器。相應方法為rotationAngleChanged:方法。
-
rotationAngleChanged:方法簡單更新了UIImageView的transform屬性,讓圖片隨著knob control的移動而旋轉。
具體怎麼樣使用RWKnobControl,可以看一下上一篇教程,那裡解釋了怎麼樣去建立它。
編譯並執行,你就能看到一款簡單的應用,當你改變knob control的值時圖片就會旋轉。
打包(Bundle)資源
你有沒有注意到RWUIControls的framework只包含了程式碼和標頭檔案,其他的檔案卻沒有被包含。例如,你沒有使用其他任何資源,比如圖片。這是iOS的一個限制,framework只能包含標頭檔案和靜態庫。
現在準備好,這篇教程要開始進階了。這一部分你將學到怎麼樣通過使用bundle整合資源,讓其可以隨著framework一起釋出,進而突破這一限制。
你將建立一個新的UI控制元件——絲帶控制元件,作為RWUIControls庫的一部分。這個控制元件將在一個UIView的右上方展示一個絲帶圖片。
建立一個Bundle
資源都會被新增到bundle中,這將是RWUIControls工程上的另一個目標。
開啟UIControlDevApp工程,選擇RWUIControls子工程,點選Add Target按鈕,導航到OS X/Framework and Library/Bundle。將新的Bundle命名為RWUIControlsResources,然後從framework選擇框中選擇Core Foundation。
這裡需要配置幾個編譯設定,因為你正在建立一個在iOS上使用的bundle,這與預設的OS X不同。選擇RWUIControlsResources目標,然後點選Build Settings欄,搜尋base sdk,選擇Base SDK這一行,按下delete鍵,這一步將OS X切換為iOS。
同時你需要將工程名稱改為RWUIControls。搜尋product name,雙擊進入編輯模式,將${TARGET_NAME}替換為RWUIControls。
預設情況下,有兩種resolutions的圖片可以產生一些有趣的現象。例如,當你匯入一個retina @2x版本的圖片時,普通版的和Retina版的將會合併成一個多resolution的TIFF(標籤影象檔案格式,Tagged Image File Format)。這不是一件好事。搜尋hidpi將COMBINE_HIDPI_IMAGES設定為NO。
現在,你將確保當你編譯framework時,bundle也能被編譯並將framework作為依賴新增到集體目標中。選中Framework目標,選擇Build Phases欄,展開Target Dependencies面板,點選 + 按鈕,選擇RWUIControlsResources目標將其新增為依賴。