天天看點

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添加進來。

framework制作

如果不結合頭檔案,靜态庫是沒有用的,靜态庫編譯一組檔案,在這些檔案中類和方法都以二進制資料的形式存在。在你建立的庫中,有些類将能夠被公開通路到,有些類隻能由庫内部通路并使用。

接下來,你需要在build欄中添加新的phase,來包含所有頭檔案,并将它們放到編譯器可以擷取到的某個地方。然後,你将會拷貝這些到你的framework中。

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

Note:如果你發現按上面找到的菜單項是灰色的(不可點選的),點選下方Build Phases界面的白色區域來擷取Xcode的應用焦點,然後重新試一下。

framework制作

把RWUIControls.h從項目導航欄中拖到中央面闆的Copy Headers下的Public部分。這一步確定任何使用你的庫的使用者均可以擷取該頭檔案。

framework制作

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欄。

framework制作

或者,你可能會發現,更簡單的方法是,編輯檔案,改變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的頭檔案可以在此看到。

建立一個Framework

到現在,你可能迫不及待地點着腳趾頭,想着什麼時候framework可以出來。可以了解,因為到現在為止你已經做了許多工作,然而卻沒有看到過framework的身影。

現在該有所改變了,你之是以到現在都沒有建立一個framework,是因為framework本身就是靜态庫加上一組頭檔案——實際上正是你已經建立好的東西。

當然,framework也有幾點不同之處:

  1. 目錄結構。Framework有一個能被Xcode識别的特殊的目錄結構,你将會建立一個build task,由它來為你建立這種結構。
  2. 片段(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

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靜态庫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制作

這個目标的主要編譯部分是多平台編譯,你将使用一個腳本來做到這一點。和你之前做的一樣,在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"

把下面的代碼加到腳本的底部。

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}"

}

把下面的代碼添加到腳本的底部。

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

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

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}"

腳本的最後是簡單的拷貝指令,将下面代碼添加到腳本最後:

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集合方案(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指令,你可以看到這些片段的一個分組。

繼續閱讀