iOS開發——建立你本身的Framework

(原文:How to Create a Framework for iOS 做者:Sam Davies 譯者:Mr_cyz )html

上一篇教程(中譯版)中,你學到了怎麼樣建立一個可複用的圓形旋鈕控件。然而你可能不清楚怎樣讓其餘開發者更方便地去複用它。ios

若是你想將你開發的控件與別人分享,一種方法是直接提供源代碼文件。然而,這種方法並非很優雅。它會暴露全部的實現細節,而這些實現你可能並不想開源出來。此外,開發者也可能並不想看到你的全部代碼,由於他們可能僅僅但願將你的這份漂亮代碼的一部分植入本身的應用中。git

另外一種方法是將你的代碼編譯成靜態庫(library),讓其餘開發者添加到本身的項目中。然而,這須要你一併公佈全部的公開的頭文件,實在是很是不方便。github

你須要一種簡單的方法來編譯你的代碼,這種方法應該使得你的代碼易分享,而且在多個工程中易複用。你須要的是一種方法來打包你的靜態庫,將全部的頭文件放到一個單元中,這樣你就能夠馬上將其加入到你的項目中並使用。正則表達式

很是幸運,這正是本篇教程所要解決的問題。你將會學到製做並使用Framework,幫助你解決這個頭疼的問題。OS X完美地支持這一點,由於Xcode就提供了一個項目模板,包含着默認構建目標(target)和能夠容納相似於圖片、聲音、字體等資源的文件。你能夠爲iOS建立Framework,不過這是一個比較複雜的手工活,若是你跟着教程走,你將學到怎麼樣跨過路障,順利地完成Framework的建立。macos

當你跟着這篇教程走完後,你將可以:xcode

  • 使用Xcode構建一個基本的靜態庫工程。架構

  • 依賴於該靜態庫工程構建一款應用。app

  • 掌握如何將靜態庫工程轉換爲完整的、合格的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新建一個靜態庫工程.

ios_framework_creating_static_lib-700x482.png

將工程命名爲RWUIControls,而後將工程保存到一個空目錄下。

ios_framework_options_for_static_lib-700x476.png

一個靜態庫工程由頭文件和實現文件組成,這些文件將被編譯爲庫自己。

爲了方便其餘開發者使用你的庫和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添加進來。

ios_framework_add_uikit_dependency.gif

若是不結合頭文件,靜態庫是沒有用的,靜態庫編譯一組文件,在這些文件中類和方法都以二進制數據的形式存在。在你建立的庫中,有些類將可以被公開訪問到,有些類只能由庫內部訪問並使用。

接下來,你須要在build欄中添加新的phase,來包含全部頭文件,並將它們放到編譯器能夠獲取到的某個地方。而後,你將會拷貝這些到你的framework中。

依然是在Xcode的Build Phases界面,選擇Editor\Add Build Phase\Add Copy Headers Build Phase。

Note:若是你發現按上面找到的菜單項是灰色的(不可點擊的),點擊下方Build Phases界面的白色區域來獲取Xcode的應用焦點,而後從新試一下。

ios_framework_add_copy_headers_build_phase.gif

把RWUIControls.h從項目導航欄中拖到中央面板的Copy Headers下的Public部分。這一步確保任何使用你的庫的用戶都可以獲取該頭文件。

ios_framework_add_header_to_public.gif

Note:顯然,全部包含在你的公共頭文件中的頭文件必須是對外公開的,這一點很是重要。不然,開發者在使用你的庫時會獲得編譯錯誤。若是Xcode在讀取公共頭文件時不能讀到你忘記設爲public的頭文件,這實在是太使人沮喪了。

建立一個UI控件

既然你已經設置好你的工程了,是時候爲你的庫添加一些功能了。因爲本篇教程的關鍵在於教你怎麼樣建立一個framework,而不是怎麼樣構建一個UI控件,這裏你將使用上一篇教程中建立好的控件。在你以前下載好的壓縮包文件中找到RWKnobControl目錄,從Finder中拖到Xcode下RWUIControls目錄下。

ios_framework_drop_rwuiknobcontrol_from_finder-700x466.png

選擇Copy items into destination group’s folder,點擊下方的選擇框,確保RWUIControls靜態庫目標被選中。

ios_framework_import_settings_for_rwknobcontrol-700x475.png

這一步默認把實現文件添加到編譯列表,把頭文件添加到Project組。這意味着它們目前是私有的。

ios_framework_default_header_membership-700x327.png

Note:在你弄清楚以前,這三個組的名稱可能會讓你迷惑,Public是你指望的,Private下的頭文件依然是能夠暴露出來的,所以名字可能有些誤導。諷刺的是,在Project下的頭文件對你的工程來講纔是「私有」的,所以,你將會更多地但願你的頭文件或者在Public下,或者在Project下。

如今,你須要將控件的頭文件RWKnobControl.h分享出來,有幾種方式能夠實現這一點,首先是在Copy Headers面板中將這個頭文件從Project欄拖到Public欄。

ios_framework_drag_header_to_public.gif

或者,你可能會發現,更簡單的方法是,編輯文件,改變Target Membership面板下的membership。這個選項更方便一些,可讓你不斷添加文件,擴充你的庫。

ios_framework_header_membership-407x320.png

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,在彈出視圖中鍵入如圖所示內容:

ios_framework_public_headers_path-700x174.png

一會你就會看到這個目錄了。

如今你須要改變一些其餘的設置,尤爲是那些在二進制庫中遺留下的設置,編譯器提供給你一個選項,來消除無效代碼:永遠不會被執行的代碼。固然你也能夠移除掉一些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。

ios_framework_successful_first_build-700x454.png

再此目錄下,你將看到靜態庫,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組下。

ios_framework_import_library_into_dev_app-700x357.png

如今你能夠在你的工程中導航到庫工程了,這樣作很是好,由於這樣意味着你能夠在庫中編輯代碼,而且運行示例工程來測試你作的改變。

Note:你沒法將同一工程在兩個Xcode窗口中同時打開,若是你發現你沒法在你的工程中導航到庫工程的話,檢查一下是否庫工程在其餘Xcode窗口中打開了。

這裏你能夠拷貝代碼,而不是和上一個教程似的從新建立代碼。首先,選擇Main.storyboard, RWViewController.h 和 RWViewController.m,而後右鍵單擊,選擇Delete,將它們刪除到廢紙簍中。而後,將你以前下載的壓縮文件中DevApp文件夾拷貝到Xcode的UIControlDevApp組下。

ios_framework_adding_files_to_dev_app.gif

如今,你將添加靜態庫做爲實例項目的依賴庫:

  • 在項目導航欄中選擇UIControlDevApp。

  • 導航到UIControlDevApp目標下Build Phases面板下。

  • 打開Target Dependencies面板,點擊+按鈕調出選擇器。

  • 找到RWUIControls靜態庫,選擇並點擊Add。這一步代表當構建dev應用時,Xcode會檢查是否靜態庫須要從新構建。

爲了鏈接到靜態庫自己,展開Link Binary With Libraries面板,再次點擊+按鈕,從Workspace組中選擇libRWUIControls.a而後點擊Add。

這一步確保Xcode能夠鏈接到靜態庫,就像鏈接到系統framework(例如UIKit)同樣。

ios_framework_add_dependencies_to_dev_app.gif

編譯並運行,若是你按照以前的教程建立了一個旋鈕控件,在你眼前展現的將是與之相同的應用。

ios_framework_dev_app_buildrun1-333x500.png

像這樣使用嵌套工程的好處是你能夠對庫自己作出修改,而不用離開示例工程,即便你同時改變兩個地方的代碼也同樣。每次你編譯工程,你都要檢查是否將頭文件的public/project關係設置正確。若是實例工程中缺失了任何須要的頭文件,它都不能被編譯。

建立一個Framework

到如今,你可能火燒眉毛地點着腳趾頭,想着何時framework能夠出來。能夠理解,由於到如今爲止你已經作了許多工做,然而卻沒有看到過framework的身影。

如今該有所改變了,你之因此到如今都沒有建立一個framework,是由於framework自己就是靜態庫加上一組頭文件——實際上正是你已經建立好的東西。

固然,framework也有幾點不一樣之處:

  1. 目錄結構。Framework有一個能被Xcode識別的特殊的目錄結構,你將會建立一個build task,由它來爲你建立這種結構。

  2. 片斷(Slice)。目前爲止,當你構建庫時,僅僅考慮到當前須要的結構(architecture)。例如,i38六、arm7等,爲了讓一個framework更有用,對於每個運行framework的結構,該framework都須要構建這種結構。一會你就會建立一個新的工程,構建全部須要的結構,並將它們包含到framework中。

這一部分很是神奇,不過咱們會慢慢地來。實際上它並不像看起來那樣複雜。

Framework結構

正如以前提到的,一個framework有一個特殊的目錄結構,看起來像是這樣的:

ios_framework_directory_structure-449x320.png

如今你須要在靜態庫構建過程當中添加腳原本建立這種結構,在項目導航欄中選擇RWUIControls,而後選擇RWUIControls靜態庫目標,選擇Build Phases欄,而後選擇Editor/Add Build Phase/Add Run Script Build Phase來添加一個新的腳本。

ios_framework_framework_add_run_script_build_phase-700x271.png

這一步在build phases部分添加了一個新的面板,這容許你在構建時運行一個Bash腳本。你但願讓腳本在build的過程當中什麼時候執行,就把這個面板拖動到列表中相對應的那一位置。對於該framework工程來講,腳本最後執行,所以你可讓它保留在默認的位置便可。

ios_framework_new_run_script_build_phase-700x299.png

雙擊面板標題欄Run Script,重命名爲Build Framework。

ios_framework_rename_script-700x131.png

在腳本文本框中粘貼下面的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構建。

ios_framework_build_target_static_lib.png

在RWUIControls工程裏Products目錄下右鍵單擊libRWUIControls.a靜態庫,而後再一次選擇Show in Finder。

ios_framework_static_lib_view_in_finder-480x295.png

在此次構建目錄中你能夠看到RWUIControls.framework,能夠肯定一下這裏展現了正確的目錄結構:

ios_framework_created_framework_directory_structure-480x251.png

這算是在完成你的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_framework_add_target_button-471x320.png

找到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靜態庫。

ios_framework_add_dependency_to_framework_target.gif

這個目標的主要編譯部分是多平臺編譯,你將使用一個腳原本作到這一點。和你以前作的同樣,在Framework目標下,選擇Build Phases欄,點擊Editor/Add Build Phase/Add Run Script Build Phase,建立一個新的Run Script Build Phase。

ios_framework_framework_add_run_script_build_phase-700x271.png

雙擊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。

詳細解釋一下上面四句聲明:

  1. SDK_NAME將指代iphoneos7.0和iphonesimulator6.1,這個正則表達式取出字符串開頭不包含數字的那些字符,所以,其結果是iphoneos 或 iphonesimulator。

  2. 這個正則表達式取出SDK_NAME中表示版本用的數字,7.0或6.1等。

  3. 這裏用簡單的字符串比較來將iphonesimulator 轉換爲iphoneos,反過來也同樣。

  4. 從構建好的工程的目錄路徑的末尾找出平臺名稱,將其替換爲其餘平臺。這樣能夠確保爲其餘平臺構建的目錄能夠被找到。這是將兩個靜態庫合併的關鍵部分。

如今你能夠啓動腳本爲其餘平臺編譯,而後獲得合併兩靜態庫的結果。

在腳本最後添加下面的代碼:

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。

ios_framework_select_framework_aggregate_scheme-480x135.png

這一步將構建並在你的桌面上存放一個RWUIControls.framework。

ios_framework_built_framework_on_desktop-700x319.png

爲了檢查一下咱們的多平臺編譯真的成功了,啓動終端,導航到桌面上的framework,像下面同樣:

1
2
$ cd ~/Desktop/RWUIControls.framework
$ RWUIControls.framework  xcrun lipo -info RWUIControls

第一條指令導航到framework中,第二行使用lipo指令從RWUIControls靜態庫中獲得須要的信息,這將列出存在於該庫中的全部片斷。

ios_framework_architectures_in_fat_library-700x257.png

這裏你能夠看到,一共有五種片斷: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組中。

ios_framework_drag_sample_image_into_xcode-480x299.png

選中Copy items into destination group’s folder,點擊Finish完成導入操做。

導入一個framework的步驟幾乎相同,將RWUIControls.framework從桌面拖到Xcode中的Frameworks組下。一樣,確保選中了Copy items into destination group’s folder。

ios_framework_import_framework.gif

打開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的值時圖片就會旋轉。

ios_framework_image_viewer_rotating.gif

打包(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_framework_import_framework.gif

這裏須要配置幾個編譯設置,由於你正在建立一個在iOS上使用的bundle,這與默認的OS X不一樣。選擇RWUIControlsResources目標,而後點擊Build Settings欄,搜索base sdk,選擇Base SDK這一行,按下delete鍵,這一步將OS X切換爲iOS。

ios_framework_bundle_set_base_sdk-700x161.png

同時你須要將工程名稱改成RWUIControls。搜索product name,雙擊進入編輯模式,將${TARGET_NAME}替換爲RWUIControls。

ios_framework_bundle_set_product_name-700x206.png

默認狀況下,有兩種resolutions的圖片能夠產生一些有趣的現象。例如,當你導入一個retina @2x版本的圖片時,普通版的和Retina版的將會合併成一個多resolution的TIFF(標籤圖像文件格式,Tagged Image File Format)。這不是一件好事。搜索hidpi將COMBINE_HIDPI_IMAGES設置爲NO。

ios_framework_bundle_hidpi_images-700x234.png

如今,你將確保當你編譯framework時,bundle也能被編譯並將framework做爲依賴添加到集體目標中。選中Framework目標,選擇Build Phases欄,展開Target Dependencies面板,點擊 + 按鈕,選擇RWUIControlsResources目標將其添加爲依賴。

ios_framework_add_bundle_as_framework_dependency.gif

如今,在Framework目標的Build Phases中,打開MultiPlatform Build面板,在腳本的最後添加下述代碼:

1
2
3
# Copy the resources bundle to the user's desktop
ditto  "${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.bundle"  \
       "${HOME}/Desktop/${RW_FRAMEWORK_NAME}.bundle"

這條指令將拷貝構建好的bundle到用戶的桌面上。如今,編譯framework scheme,你會發現bundle在桌面上出現。

ios_framework_bundle_on_desktop-198x320.png

導入Bundle

爲了用這個新的bundle開發,你須要在示例項目中使用它,這意味着你必須既把它做爲依賴添加到工程中,同時做爲一個對象拷貝到項目中。

在項目導航欄中,選擇UIControlDevApp工程,點擊UIControlDevApp目標,展開RWUIControls工程的Product組,把RWUIControls.bundle拖到Copy Bundle Resources面板中的 Build Phases欄。

在Target Dependencies面板中,點擊+按鈕,添加新的依賴,而後選擇RWUIControlsResources。

ios_framework_add_bundle_for_dev_project.gif

建立一個絲帶視圖(Ribbon View)

上面的就是全部必需的配置工做了,從你以前下載的壓縮文件中將RWRibbon文件夾拖入到RWUIControls工程下RWUIControls組中。

ios_framework_drag_rwribbon-480x309.png

選中Copy the items into the destination group’s folder,在對應的選擇框中打勾,確保它被添加到RWUIControls靜態lib目標中。

ios_framework_rwribbon_membership-700x472.png

代碼中一個很重要的部分是你怎樣引用一張圖片。若是你看一下RWRibbonView.m文件中的addRibbonView方法,你將會看到相關的這一行代碼:

1
UIImage *image = [UIImage imageNamed:@ "RWUIControls.bundle/RWRibbon" ];

Bundle就像一個文件目錄,因此引用bundle中的一張圖片是很是簡單的。

將圖片添加到bundle中,選擇這張圖片,在右邊的面板中,經過選擇來表示它應該屬於RWUIControlsResources目標。

ios_framework_rwribbon_image_membership-700x208.png

還記得咱們說過要確保framework能夠被訪問嗎?如今,你須要導出頭文件RWRibbon.h,在Target Membership面板中選擇該文件,而後從彈出視圖中選擇Public。

ios_framework_rwribbon_header_membership-480x262.png

最後,你須要將頭文件引用添加到framework的頭文件中。打開RWUIControls.h添加下面這兩行:

1
2
// RWRibbon
#import < RWUIControls/RWRibbonView.h>

將絲帶添加到示例工程中

在UIControlDevApp項目中打開RWViewController.m文件,在@interface後的大括號中添加下面的實例變量聲明。

1
RWRibbonView  *_ribbonView;

在viewDidLoad:的末尾添加下面的代碼來建立一個絲帶視圖:

1
2
3
4
5
6
7
// Creates a sample ribbon view
_ribbonView = [[RWRibbonView alloc] initWithFrame:self.ribbonViewContainer.bounds];
[self.ribbonViewContainer addSubview:_ribbonView];
// Need to check that it actually works :)
UIView *sampleView = [[UIView alloc] initWithFrame:_ribbonView.bounds];
sampleView.backgroundColor = [UIColor lightGrayColor];
[_ribbonView addSubview:sampleView];

編譯並運行UIControlDevApp scheme。你將看到新的絲帶控件出如今應用的下方。

ios_framework_dev_app_with_ribbon-333x500.png

在ImageViewer中使用Bundle

我要向你分享的最後一件事是怎麼樣在其餘應用中使用這個新的bundle,例如,你以前建立的ImageViewer應用。

開始以前,確保你的bundle和framework都是最新版本的,選擇Framework scheme而後按下cmd+B編譯。

打開ImageViewer工程,找到Frameworks組中的RWUIControls.framework項目,而後將其刪除,選擇Move to Trash。而後將RWUIControls.framework從你的桌面上拖到Frameworks組中。這是必須的,由於此時的framework已經與你第一次導入時的framework大不相同了。

ios_framework_delete_framework-700x283.png

Note:若是Xcode拒絕讓你添加framework,這多是由於你並無真正將以前版本的framework刪除到廢紙簍。若是是由於這樣的話,從Finder中ImageViewer目錄下刪除framework而後從新嘗試。

導入bundle,簡單將其從桌面上拖到ImageViewer組中。選中Copy items into destination group’s folder,選中對應的選擇框,確保它被添加到ImageViewer目標中。

ios_framework_import_bundle-700x474.png

接下來你要將絲帶添加到能夠旋轉的圖片上。所以,在RWViewController.m文件中代碼要有一些簡單的變更。

打開該文件,將屬性imageView的類型從UIImageView變爲RWRibbonView:

1
@property (nonatomic, strong) RWRibbonView *imageView;

將viewDidLoad方法中第一部分,負責建立並配置UIImageView的代碼,替換爲下面的代碼:

1
2
3
4
5
6
7
8
9
10
[ super  viewDidLoad];
// Create UIImageView
CGRect frame = self.view.bounds;
frame.size.height *= 2/3.0;
self.imageView = [[RWRibbonView alloc] initWithFrame:CGRectInset(frame, 0, 20)];
UIImageView *iv = [[UIImageView alloc] initWithFrame:self.imageView.bounds];
iv.image = [UIImage imageNamed:@ "sampleImage.jpg" ];
iv.contentMode = UIViewContentModeScaleAspectFit;
[self.imageView addSubview:iv];
[self.view addSubview:self.imageView];

編譯並運行該項目,如今該項目中你同時使用了RWUIControls framework下的RWKnobControl和RWRibbonView。

ios_framework_image_viewer_with_ribbon.gif

如今該幹什麼?

在本篇教程中,你學到了關於建立一個framework並在你的iOS app中使用所需的一切知識,包括開發一個framework的最好的方式,以及怎麼樣使用bundle來共享資源。

有沒有一個你喜歡的功能在多個app中使用了呢?如今你所學到的概念能夠幫你建立一個可複用的庫,使你的編碼更加簡單。Framework提供了一種優雅的方式來得到庫中的代碼,讓你在寫一個炫酷的app的時候,能夠靈活地獲取到你須要的一切。

完整工程的源碼被放到了Github上,每一步都有一個commit。或者你能夠從這裏下載完整的壓縮文件。

相關文章
相關標籤/搜索