2018 Xcode8 創(chuàng)建自己的Framework(一)

新年定個小目標,比如先學習組件化開發(fā)o( ̄▽ ̄)d
最近研究組件化開發(fā),涉及自己生成靜態(tài)庫,創(chuàng)建Framework。查詢資料的過程中,發(fā)現(xiàn)一個超級實用的譯文教程,然而這個超實用的譯文教程因其發(fā)表于2015年,部分內(nèi)容已有出入(盡管不大??????),特在這里邊學習邊記錄,因理解能力有限,部分內(nèi)容仍有引用自原文。

尊重原創(chuàng)作者,原譯文地址在此:iOS開發(fā)——創(chuàng)建你自己的Framework

原文地址在此:How to Create a Framework for iOS

這里是學習記錄過程,如果有大佬能不吝賜教,榮幸之至??????

文中所涉及工程資源:

1.一個可重用的旋鈕(即我們學習過程中需要封裝的控件),下載地址:點此 <很重要>

2.對此控件開發(fā)過程感興趣的朋友可以移步原譯文:iOS自定義控件教程:制作一個可重用的旋鈕

概念補充:
庫,分為靜態(tài)庫和動態(tài)庫,本質(zhì)上來說是可執(zhí)行代碼的集合。非獨立程序,為其他程序提供服務。

  1. 靜態(tài)庫,存在兩種形式.a 和.framework。在實際使用當中,兩者的關系可以簡單的用一個公式標明
    .framework = .a + .h
  2. 動態(tài)庫,存在.framework和.tbd兩種形式。這個兩種形式在系統(tǒng)庫中很常見,蘋果在 iOS8 以后為我們提供了Cocoa Touch Framework 來有條件的創(chuàng)建和使用動態(tài)庫

創(chuàng)建一個靜態(tài)庫工程

打開 Xcode,新建一個靜態(tài)庫工程,如下圖示

Static Library

創(chuàng)建完工程之后,會發(fā)現(xiàn)自動創(chuàng)建了一個.h和.m 文件。為了方便Framework使用,集中在該.h 文件中導入想要公開的類,在這里無需實現(xiàn).m,所以刪除.m 文件。到這里,我們可在工程放入我們想要封裝的工具或者控件了。

因為這個學習教程是一個封裝控件作為主要工作的,所以我們要添加一些的必要的依賴,我這里添加的依賴如下如所示,實際開發(fā)中可能還需要添加別的依賴,視情況酌情添加。這里記得我們要把導入工程的文件,替換#import<Foundation/Foundation.h> 為#import<UIKit/UIKit.h>哦

依賴添加

在當前的 Build Phases ,還需要添加一個Header Phase,依次選擇導航欄Editor—>Add Build Phase —> Add Headers Build Phase ,

Headers Phases

添加完成后,展開 Header Phase,將.h 文件拖入 public。

在這里引用原譯文對 headers 里面的幾個組頭名進行解釋,應該會幫助了解到這里的作用:

Note:在你弄清楚之前,這三個組的名稱可能會讓你迷惑,Public是你期望的,Private下的頭文件依然是可以暴露出來的,因此名字可能有些誤導。諷刺的是,在Project下的頭文件對你的工程來說才是“私有”的,因此,你將會更多地希望你的頭文件或者在Public下,或者在Project下。

敲黑板,在這里要注意到的是,所有導入到該.h 中的文件必須也是對外公開的,也就是導入到.h的文件也必須出現(xiàn)在 public這個地方。否則,導入到工程里面編譯報錯。

在這里我們還需要把要暴露的控件頭文件拖到 public 當中哦。

創(chuàng)建 UI 控件

這里我們暫時只學習創(chuàng)建靜態(tài)庫,控件直接從原作的 Git Hub 上下載并將所需文件拖進來的,這里我選擇了原作主要的控件類。實際開發(fā)中,可以自行添加(內(nèi)心 OS:在這個靜態(tài)庫里開發(fā),又看不到控件的樣子?。?!別著急,這么影響開發(fā)的問題原作已為我們考慮到,后面會介紹??????)

當你把這幾個文件拖進來后,你就會發(fā)現(xiàn),它們的頭文件已經(jīng)被放入 Headers 的 project 中,

光是把要暴露的頭文件放在 public 中,還無法是使用者找到這個控件的,所以我們要在工程創(chuàng)建時我們留下的那個.h 文件中,導入頭文件哦。這樣,在實際使用當中,它的導入會是這個樣子的

#import <ToolsFoundation/ToolsFoundation.h>

就像我們平時導入 UIKit 一樣,

#import <UIKit/UIKit.h>

配置 Build Settings

這里我們需要在工程名靜態(tài)庫里的 Build settings 做一些事情,

這里我們搜索 public,在 Public Headers Folder Path 這個地方雙擊,修改內(nèi)容為如下:

include/$(PROJECT_NAME)

這里在原譯文中所給出的理由是這樣的:

你需要提供一個目錄名,表示你將把拷貝的公共頭文件存放到哪里。這樣確保當你使用靜態(tài)庫的時候可以定位到相關頭文件的位置

我們在開發(fā)的過程中,可能存在一些實際使用時永遠不會被執(zhí)行的代碼和debug相關內(nèi)容。這里對使用者用處不大,我們可以禁用掉這些功能,仍然在 Build Setings 中,搜索以下內(nèi)容,做出對應的設置

? Dead Code Stripping 設置為NO 延伸: 《Dead Code Stripping》

? Strip Debug Symbol During Copy 全部設置為NO

? Strip Style 設置為Non-Global Symbols

選擇 target 設備,到這里編譯運行,如果沒錯那是好的,如果有錯誤看看提示,視具體情況解決。編譯成功后,可以發(fā)現(xiàn)之前 product 文件夾下的.a 文件名由紅變黑了。點擊右鍵在訪達中顯示.a文件所在位置

在這里可以看到我們之前想要公開的頭文件和 .a 文件都已經(jīng)生成了,怎么樣是不是和我們之前用的其他的 Framework 越來越像了呢,有暴露的頭文件和.a 靜態(tài)庫。But~這里的.framework文件呢?哈哈哈,欲知 Framework 何在且看下文。。。

創(chuàng)建一個聯(lián)調(diào)工程

聯(lián)調(diào)工程,什么鬼?就是我們又創(chuàng)建一個新的工程,工程里面我們會用到我們的剛才靜態(tài)庫文件,在這里,我們便于在靜態(tài)庫工程里開發(fā),聯(lián)調(diào)工程里測試。怎么樣?哈哈,調(diào)試不要太方便哦\( ̄︶ ̄)/。

這里的操作步驟,我們要在剛剛靜態(tài)庫工程所在的文件家里同樣創(chuàng)建一個新的開發(fā)工程哦,single view application 哦。關閉之前的靜態(tài)庫工程,把靜態(tài)庫文件夾中.xcodeproj工程文件拖到開發(fā)工程。

note:這里需要注意的是這個靜態(tài)庫工程拖入進來后就算是打開了,這個工程無法在別的地方開啟了。

這里面,先刪除了默認創(chuàng)建的 viewcontroller的.h、.m 和 main.storyboard 文件。從下載的控件文件夾中,直接把原來RWViewController.h、RWViewController.m 和 mian.storyboard 拖進來。(當然我這里是偷懶步驟,實際開發(fā)中可以結(jié)合自己要開發(fā)的工程進行測試)。拖進來之后,在下圖位置中添加控件的靜態(tài)庫文件。

在你所需要測試的文件中import 靜態(tài)庫暴露的頭文件,現(xiàn)在可以這樣導入了

驚不驚喜,意不意外,看著有點樣子了,哈哈哈哈哈

創(chuàng)建 Framework

Framework 本身的結(jié)構(gòu)就是靜態(tài)庫加一組頭文件。你可能覺得已經(jīng)創(chuàng)建好,畢竟在 .a 文件所在文件中你看到了 include 的里面已經(jīng)有了一組頭文件。

這里還需要記錄 Framework 的兩個特點:

1. 目錄結(jié)構(gòu)。Framework有一個能被Xcode識別的特殊的目錄結(jié)構(gòu),你將會創(chuàng)建一個build task,由它來為你創(chuàng)建這種結(jié)構(gòu)。

2. 片段(Slice)。目前為止,當你構(gòu)建庫時,僅僅考慮到當前需要的結(jié)構(gòu)(architecture)。例如,i386、arm7等,為了讓一個framework更有用,對于每一個運行framework的結(jié)構(gòu),該framework都需要構(gòu)建這種結(jié)構(gòu)。一會你就會創(chuàng)建一個新的工程,構(gòu)建所有需要的結(jié)構(gòu),并將它們包含到framework中。

一下是一個來自原文的 Framework 結(jié)構(gòu)表

我們可以通過添加一段腳本,來創(chuàng)建這種結(jié)構(gòu)。添加方法,看圖??????

image

看清楚了嘛,紅色框選位置1,沒錯就是我們要開發(fā)的靜態(tài)庫工程文件,點它喵的,然后target控件靜態(tài)庫Build Phases,點它喵的。按照圖示步驟來,都 ??的,我這里之所以出現(xiàn)不可選中,是因為我已經(jīng)點了創(chuàng)建了,嘻嘻~創(chuàng)建好之后我們把它的名字Run Script改為 Build Framework。然后把如下代碼貼進去:


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"

現(xiàn)在選擇靜態(tài)庫工程,選擇iOS device,Build 一下。在靜態(tài)庫工程的 product 里面找到生成的.a文件。右鍵在 finder 里面顯示看看

image

在這里是不是看著離目標一步一步接近了?哈哈哈,這里我們已經(jīng)完成大部分的工作了。然鵝,這里還沒有靜態(tài) lib 文件。這就是下一步我們將要做的

多架構(gòu)支持

簡單的說 app 可以運行在不同架構(gòu)的設備上,那我們的 Framework 也要做到支持多架構(gòu)哦。

arm7: 在最老的支持iOS7的設備上使用

arm7s: 在iPhone5和5C上使用

arm64: 運行于iPhone5S的64位 ARM 處理器 上

i386: 32位模擬器上使用

x86_64: 64為模擬器上使用

下面的一段 js 代碼將幫助我們實現(xiàn)多架構(gòu)的支持, 并將模擬器和真機的framework合并到一起了,詳細原理,我還沒吃透(看不懂。。。)所以先上步驟。為了方便向 run script統(tǒng)一添加將每個步驟的代碼放到一起了,不過沒關系,代碼中有各個步驟英文注解

1. 你可以用我們上文用到的開發(fā)工程(聯(lián)調(diào)工程),也可以用靜態(tài)庫工程。去在 target 里面新建一個 iOS/Framework。在這里我直接在開發(fā)工程里面創(chuàng)建了,以下是在開發(fā)工程(聯(lián)調(diào)工程)里操作的一些步驟:

如上圖所示,點擊 target 下的小加號,選擇創(chuàng)建一個 Framework 工程,在新建的庫工程的 Build phases 里面添加依賴。這個依賴就是我們之前生成.a靜態(tài)庫。

接下來,仍是在 Build phases 中添加一段腳本語言,進行到這一步,真是完全懵逼過來的,那些腳本看不懂啊(╥╯^╰╥),只能復制粘貼了。添加腳本的方法仍與前文提到的相同,不再贅述。仍舊是雙擊 run script ,改名字為MultiPlatform Build。

添加如下腳本代碼:


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"

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

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

# 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,編譯


編譯

編譯成功后,你將發(fā)現(xiàn)在桌面上生成了一個Framework



接下來我們需要驗證下我們生成的這個 Framework 是否真的支持多架構(gòu)。
打開終端,cd 這個 .framework 鍵入一下命令
lipo -info xxx.framework
lipo.png

得到如上信息,表明成功了!(づ??????)づ

實踐是檢驗真理的唯一標準,接下來你可以自己創(chuàng)建一個新的工程,去把這個生成的.framework導入工程中,然后測試一下吧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容