iOS里的動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)


介紹

  • 動(dòng)態(tài)庫(kù)形式:.dylib和.framework

  • 靜態(tài)庫(kù)形式:.a和.framework

  • 動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)的區(qū)別

靜態(tài)庫(kù):鏈接時(shí),靜態(tài)庫(kù)會(huì)被完整地復(fù)制到可執(zhí)行文件中,被多次使用就有多份冗余拷貝(圖1所示)

系統(tǒng)動(dòng)態(tài)庫(kù):鏈接時(shí)不復(fù)制,程序運(yùn)行時(shí)由系統(tǒng)動(dòng)態(tài)加載到內(nèi)存,供程序調(diào)用,系統(tǒng)只加載一次,多個(gè)程序共用,節(jié)省內(nèi)存(圖2所示)

上圖中的綠框表示app的可執(zhí)行文件。

動(dòng)態(tài)庫(kù)的作用

應(yīng)用插件化:

每一個(gè)功能點(diǎn)都是一個(gè)動(dòng)態(tài)庫(kù),在用戶想使用某個(gè)功能的時(shí)候讓其從網(wǎng)絡(luò)下載,然后手動(dòng)加載動(dòng)態(tài)庫(kù),實(shí)現(xiàn)功能的的插件化

雖然技術(shù)上來說這種動(dòng)態(tài)更新是可行的,但是對(duì)于AppStore上上架的app是不可以的。iOS8之后雖然可以上傳含有動(dòng)態(tài)庫(kù)的app,但是蘋果不僅需要你動(dòng)態(tài)庫(kù)和app的簽名一致,而且蘋果會(huì)在你上架的時(shí)候再經(jīng)過一次AppStore的簽名。所以你想在線更新動(dòng)態(tài)庫(kù),首先你得有蘋果APPStore私鑰,而這個(gè)基本不可能。

除非你的應(yīng)用不需要通過AppStore上架,比如企業(yè)內(nèi)部的應(yīng)用,通過企業(yè)證書發(fā)布,那么就可以實(shí)現(xiàn)應(yīng)用插件化在線更新動(dòng)態(tài)庫(kù)了。

共享可執(zhí)行文件:

在其它大部分平臺(tái)上,動(dòng)態(tài)庫(kù)都可以用于不同應(yīng)用間共享,這就大大節(jié)省了內(nèi)存。從目前來看,iOS仍然不允許進(jìn)程間共享動(dòng)態(tài)庫(kù),即iOS上的動(dòng)態(tài)庫(kù)只能是私有的,因?yàn)槲覀內(nèi)匀徊荒軐?dòng)態(tài)庫(kù)文件放置在除了自身沙盒以外的其它任何地方。

不過iOS8上開放了App Extension功能,可以為一個(gè)應(yīng)用創(chuàng)建插件,這樣主app和插件之間共享動(dòng)態(tài)庫(kù)還是可行的。(還需了解下App Extension)

Xcode6之后支持創(chuàng)建動(dòng)態(tài)庫(kù)工程

Xcode6之后蘋果在iOS上開放了動(dòng)態(tài)庫(kù)。

創(chuàng)建:File->New->Project

創(chuàng)建

我們上面說過Framework即可以是動(dòng)態(tài)庫(kù),也可以是靜態(tài)庫(kù)。那么我們上圖中默認(rèn)創(chuàng)建的是動(dòng)態(tài)庫(kù),那么如何創(chuàng)建動(dòng)態(tài)庫(kù)呢?比如我創(chuàng)建的framework叫testLib,然后在build setting中設(shè)置動(dòng)態(tài)庫(kù)或靜態(tài)庫(kù)。如下圖,創(chuàng)建framework的時(shí)候默認(rèn)是Dynamic Library,我們可以修改為Static Library。

如果我們創(chuàng)建的framework是動(dòng)態(tài)庫(kù),那么我們直接在工程里使用的時(shí)候會(huì)報(bào)錯(cuò):Reason: Image Not Found。需要在工程的General里的Embedded Binaries添加這個(gè)動(dòng)態(tài)庫(kù)才能使用。
因?yàn)槲覀儎?chuàng)建的這個(gè)動(dòng)態(tài)庫(kù)其實(shí)也不能給其他程序使用的,而你的App ExtensionAPP之間是需要使用這個(gè)動(dòng)態(tài)庫(kù)的。這個(gè)動(dòng)態(tài)庫(kù)可以App ExtensionAPP之間共用一份(App 和 Extension 的 Bundle 是共享的),因此蘋果又把這種 Framework 稱為 Embedded Framework,而我把這個(gè)動(dòng)態(tài)庫(kù)稱為偽動(dòng)態(tài)庫(kù)。

具體創(chuàng)建靜態(tài)庫(kù)和Framework可以參考:Xcode7創(chuàng)建靜態(tài)庫(kù)和Framework。

自己創(chuàng)建的動(dòng)態(tài)庫(kù)

我們創(chuàng)建的動(dòng)態(tài)庫(kù)和系統(tǒng)的動(dòng)態(tài)庫(kù)有什么區(qū)別呢?我們創(chuàng)建的動(dòng)態(tài)庫(kù)是在我們自己應(yīng)用的.app目錄里面,只能自己的App ExtensionAPP使用。而系統(tǒng)的動(dòng)態(tài)庫(kù)是在系統(tǒng)目錄里面,所有的程序都能使用。

可執(zhí)行文件和自己創(chuàng)建的動(dòng)態(tài)庫(kù)位置:

一般我們得到的iOS程序包是.ipa文件。其實(shí)就是一個(gè)壓縮包,解壓縮.ipa。解壓縮后里面會(huì)有一個(gè)payload文件夾,文件夾里有一個(gè).app文件,右鍵顯示包內(nèi)容,然后找到一個(gè)一般體積最大跟.app同名的文件,那個(gè)文件就是可執(zhí)行文件。
而我們?cè)谀M器上運(yùn)行的時(shí)候用NSBundle *bundel = [[NSBundle mainBundle] bundlePath];就能得到.app的路徑??蓤?zhí)行文件就在.app里面。

而我們自己創(chuàng)建的動(dòng)態(tài)庫(kù)就在.app目錄下的Framework文件夾里。

下圖就是測(cè)試工程DFCUserInterface.app的目錄

我這里用了一個(gè)測(cè)試工程,即有系統(tǒng)的動(dòng)態(tài)庫(kù)(WebKit),又有自己的動(dòng)態(tài)庫(kù)(DFCUserInterface),我們可以看一下可執(zhí)行文件中對(duì)動(dòng)態(tài)庫(kù)的鏈接地址。用MachOView查看可執(zhí)行文件。其中@rpth這個(gè)路徑表示的位置可以查看Xcode 中的鏈接路徑問題,而現(xiàn)在表示的其實(shí)就是.app下的Framework文件夾。

下圖表示了靜態(tài)庫(kù),自己創(chuàng)建的動(dòng)態(tài)庫(kù)和系統(tǒng)動(dòng)態(tài)庫(kù):


簽名

系統(tǒng)在加載動(dòng)態(tài)庫(kù)時(shí),會(huì)檢查 framework 的簽名,簽名中必須包含 TeamIdentifier 并且 framework 和 host app 的 TeamIdentifier 必須一致。
我們?cè)贒ebug測(cè)試的時(shí)候是不會(huì)報(bào)錯(cuò)的,在打包時(shí)如果有動(dòng)態(tài)庫(kù),那么就會(huì)檢查TeamIdentifier。

如果不一致,否則會(huì)報(bào)下面的錯(cuò)誤:

Error loading /path/to/framework: dlopen(/path/to/framework, 265): no suitable image found. Did find:/path/to/framework: mmap() error 1

此外,如果用來打包的證書是 iOS 8 發(fā)布之前生成的,則打出的包驗(yàn)證的時(shí)候會(huì)沒有 TeamIdentifier 這一項(xiàng)。這時(shí)在加載 framework 的時(shí)候會(huì)報(bào)下面的錯(cuò)誤:

[deny-mmap] mapped file has no team identifier and is not a platform binary:/private/var/mobile/Containers/Bundle/Application/5D8FB2F7-1083-4564-94B2-0CB7DC75C9D1/YourAppNameHere.app/Frameworks/YourFramework.framework/YourFramework

可以通過 codesign 命令來驗(yàn)證。

codesign -dv /path/to/YourApp.app
或
codesign -dv /path/to/youFramework.framework

如果證書太舊,輸出的結(jié)果如下:

Executable=/path/to/YourApp.app/YourApp
Identifier=com.company.yourapp
Format=bundle with Mach-O thin (armv7)
CodeDirectory v=20100 size=221748 flags=0x0(none) hashes=11079+5 location=embedded
Signature size=4321
Signed Time=2015年10月21日 上午10:18:37
Info.plist entries=42
TeamIdentifier=not set
Sealed Resources version=2 rules=12 files=2451
Internal requirements count=1 size=188

注意其中的 TeamIdentifier=not set。

我們?cè)谟?code>cocoapods的use_framework!的時(shí)候生成的動(dòng)態(tài)庫(kù)也可以用codesign -dv /path/to/youFramework.framework查看到TeamIdentifier=not set。關(guān)于動(dòng)態(tài)庫(kù)的簽名TeamIdentifier等之前沒接觸過,可以再去查看一下資料。

關(guān)于Framework

  • framework為什么既是靜態(tài)庫(kù)又是動(dòng)態(tài)庫(kù)?

系統(tǒng)的.framework是動(dòng)態(tài)庫(kù),我們自己建立的.framework一般都是靜態(tài)庫(kù)。但是現(xiàn)在你用xcode創(chuàng)建Framework的時(shí)候默認(rèn)是動(dòng)態(tài)庫(kù),一般打包成SDK給別人用的話都使用的是靜態(tài)庫(kù),可以修改Build SettingsMach-O TypeStatic Library

  • 什么是framework

Framework是Cocoa/Cocoa Touch程序中使用的一種資源打包方式,可以將代碼文件、頭文件、資源文件、說明文檔等集中在一起,方便開發(fā)者使用。一般如果是靜態(tài)Framework的話,資源打包進(jìn)Framework是讀取不了的。靜態(tài)Framework和.a文件都是編譯進(jìn)可執(zhí)行文件里面的。只有動(dòng)態(tài)Framework能在.app下面的Framework文件夾下看到,并讀取.framework里的資源文件。

Cocoa/Cocoa Touch開發(fā)框架本身提供了大量的Framework,比如Foundation.framework/UIKit.framework/AppKit.framework等。需要注意的是,這些framework無一例外都是動(dòng)態(tài)庫(kù)。

平時(shí)我們用的第三方SDK的framework都是靜態(tài)庫(kù),真正的動(dòng)態(tài)庫(kù)是上不了AppStore的(iOS8之后能上AppStore,因?yàn)橛袀€(gè)App Extension,需要?jiǎng)討B(tài)庫(kù)支持)。

創(chuàng)建靜態(tài)Framework

1.選擇Framework

創(chuàng)建

2.選擇為靜態(tài)庫(kù)


3.生成對(duì)應(yīng)版本的靜態(tài)庫(kù)

靜態(tài)庫(kù)的版本(4種)

  • 真機(jī)-Debug版本
  • 真機(jī)-Release版本
  • 模擬器-Debug版本
  • 模擬器-Release版本

這里debug或release是否生成符號(hào)表,是否對(duì)代碼優(yōu)化等可以在如何加快編譯速度查看。

我們選擇Release版本。編譯模擬器和真機(jī)的所有CPU架構(gòu)。

然后選擇模擬器或者Generic iOS Device運(yùn)行編譯就會(huì)生成對(duì)應(yīng)版本的Framework了。


4.合成包含真機(jī)和模擬器的Framework

終端cd到Products,然后執(zhí)行以下代碼,就會(huì)在Products目錄下生成新的包含兩種的執(zhí)行文件,然后復(fù)制到任何一個(gè)testLib.framework里替換掉舊的testLib就可以了。

lipo -create Release-iphoneos/testLib.framework/testLib  Release-iphonesimulator/testLib.framework/testLib  -output testLib

或者在工程的Build Phases里添加以下腳本,真機(jī)和模擬器都Build一遍之后就會(huì)在工程目錄下生成Products文件夾,里面就是合并之后的Framework。

if [ "${ACTION}" = "build" ]
then
INSTALL_DIR=${SRCROOT}/Products/${PROJECT_NAME}.framework

DEVICE_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework

SIMULATOR_DIR=${BUILD_ROOT}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework


if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi

mkdir -p "${INSTALL_DIR}"

cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/"
#ditto "${DEVICE_DIR}/Headers" "${INSTALL_DIR}/Headers"

lipo -create "${DEVICE_DIR}/${PROJECT_NAME}" "${SIMULATOR_DIR}/${PROJECT_NAME}" -output "${INSTALL_DIR}/${PROJECT_NAME}"

#open "${DEVICE_DIR}"
#open "${SRCROOT}/Products"
fi

Framework目錄

  • Headers
    表示暴露的頭文件,一般都會(huì)有一個(gè)和Framework同名的.h文件,你在創(chuàng)建Framework的時(shí)候文件夾里也會(huì)默認(rèn)生成這樣一個(gè)文件。有這個(gè)和Framework同名的.h文件@import導(dǎo)入庫(kù)的時(shí)候編譯器才能找到這個(gè)庫(kù)(@import導(dǎo)入頭文件可參考iOS里的導(dǎo)入頭文件)。

  • info.plist
    主要就是這個(gè)Framework的一些配置信息。

  • Modules
    這個(gè)文件夾里有個(gè)module.modulemap文件,我們看到這里面有這樣一句umbrella header "testLib.h",umbrella有保護(hù)傘、庇護(hù)的意思。
    也就是說Headers中暴露的testLib.h文件被放在umbrella雨傘下保護(hù)起來了,所以我們需要將其他的所有需要暴露的.h文件放到testLib.h文件中保護(hù)起來,不然會(huì)出現(xiàn)警告。@import的時(shí)候也只能找到umbrella雨傘下保護(hù)起來的.h文件。

  • 二進(jìn)制文件
    這個(gè)就是你源碼編譯而成的二進(jìn)制文件,主要的執(zhí)行代碼就在這個(gè)里面。

  • .bundle文件
    如果我們?cè)?code>Build Phases -> Copy Bundle Resources里加入.bundle文件,那么創(chuàng)建出來的.Framework里就會(huì)有這個(gè).bundle的資源文件夾。

Framework的資源文件

CocoaPods如何生成Framework的資源文件

我們能看到用cocoapods創(chuàng)建Framework的時(shí)候,F(xiàn)ramework里面有一個(gè).bundle文件,跟Framework同級(jí)目錄里也有一個(gè).bundle文件。這兩個(gè)文件其實(shí)是一樣的。

那這兩個(gè).bundle是怎么來的呢?我們能看到用use_frameworks!生成的pod里面,pods這個(gè)PROJECT下面會(huì)為每一個(gè)pod生成一個(gè)target,比如我有一個(gè)pod叫做testLib,那么就會(huì)有一個(gè)叫testLibtarget,最后這個(gè)target生成的就是testLib.framework。
那么如果這個(gè)pod有資源文件的話,就會(huì)有一個(gè)叫testLib-bundleNametarget,最后這個(gè)target生成的就是bundleName.bundle。

上面創(chuàng)建靜態(tài)Framework例子里生成資源文件

testLibtargetBuild Phases -> Copy Bundle Resources里加入這個(gè)這個(gè).bundle,在Framework里面就會(huì)生成這樣一個(gè)bundle。
testLibtargetBuild Phases -> Target Dependencies里加入這個(gè)target:testLib-bundleName,就會(huì)在Framework的同級(jí)目錄里生成這樣一個(gè)bundle。

靜態(tài)Framework里不需要加入資源文件

一般如果是靜態(tài)Framework的話,資源打包進(jìn)Framework是讀取不了的。靜態(tài)Framework和.a文件都是編譯進(jìn)可執(zhí)行文件里面的。只有動(dòng)態(tài)Framework能在.app的Framework文件夾下看到,并讀取.framework里的資源文件。

你可以用NSBundle *bundel = [[NSBundle mainBundle] bundlePath];得到.app目錄,如果是動(dòng)態(tài)庫(kù)你能在Framework目錄下看到這個(gè)動(dòng)態(tài)庫(kù)以及動(dòng)態(tài)庫(kù)里面資源文件。然后你只要用NSBundle *bundle = [NSBundle bundleForClass:<#ClassFromFramework#>];得到這個(gè)動(dòng)態(tài)庫(kù)的路徑就能讀取到里面的資源了。
但是如果是靜態(tài)庫(kù)的話,因?yàn)榫幾g進(jìn)了可執(zhí)行文件里面,你也就沒辦法讀到這個(gè)靜態(tài)庫(kù)了,你能看到.app下的Framework目錄為空。

在framework或子工程中使用xib

問題

  • 如果靜態(tài)庫(kù)中有category類,則在使用靜態(tài)庫(kù)的項(xiàng)目配置中【Other Linker Flags】需要添加參數(shù)【-ObjC]或者【-all_load】。

  • 如果使用framework的使用出現(xiàn)【Umbrella header for module 'XXXX' does not include header 'XXXXX.h'】,是因?yàn)殄e(cuò)把xxxxx.h拖到了public中。

  • 如果出現(xiàn)【dyld: Library not loaded:XXXXXX】,是因?yàn)榇虬膄ramework版本太高。比如打包framework時(shí),選擇的是iOS 9.0,而實(shí)際的工程環(huán)境是iOS 8開始的。需要到iOS Deployment Target設(shè)置對(duì)應(yīng)版本。

  • 如果創(chuàng)建的framework類中使用了.dylib或者.tbd,首先需要在實(shí)際項(xiàng)目中導(dǎo)入.dylib或者.tbd動(dòng)態(tài)庫(kù),然后需要設(shè)置【Allow Non-modular Includes ....】為YES,否則會(huì)報(bào)錯(cuò)"Include of non-modular header inside framework module"。

  • 有時(shí)候我們會(huì)發(fā)現(xiàn)在使用的時(shí)候加載不了動(dòng)態(tài)Framework里的資源文件,其實(shí)是加載方式不對(duì),比如用pod的時(shí)候使用的是use_frameworks!,那么資源是在Framework里面的,需要使用以下代碼加載(具體可參考給pod添加資源文件):

NSBundle *bundle = [NSBundle bundleForClass:<#ClassFromFramework#>];
[UIImage imageWithContentsOfFile:[bundle pathForResource:@"imageName@2x"(@"bundleName.bundle/imageName@2x") ofType:@"png"]];

Swift 支持

跟著 iOS8 / Xcode 6 同時(shí)發(fā)布的還有 Swift。如果要在項(xiàng)目中使用外部的代碼,可選的方式只有兩種,一種是把代碼拷貝到工程中,另一種是用動(dòng)態(tài) Framework。使用靜態(tài)庫(kù)是不支持的。

造成這個(gè)問題的原因主要是 Swift 的運(yùn)行庫(kù)沒有被包含在 iOS 系統(tǒng)中,而是會(huì)打包進(jìn) App 中(這也是造成 Swift App 體積大的原因),靜態(tài)庫(kù)會(huì)導(dǎo)致最終的目標(biāo)程序中包含重復(fù)的運(yùn)行庫(kù)(這是蘋果自家的解釋)。同時(shí)拷貝 Runtime 這種做法也會(huì)導(dǎo)致在純 ObjC 的項(xiàng)目中使用 Swift 庫(kù)出現(xiàn)問題。蘋果聲稱等到 Swift 的 Runtime 穩(wěn)定之后會(huì)被加入到系統(tǒng)當(dāng)中,到時(shí)候這個(gè)限制就會(huì)被去除了(參考這個(gè)問題的問題描述,也是來自蘋果自家文檔)。

CocoaPods 的做法

在純 ObjC 的項(xiàng)目中,CocoaPods 使用編譯靜態(tài)庫(kù) .a 方法將代碼集成到項(xiàng)目中。在 Pods 項(xiàng)目中的每個(gè) target 都對(duì)應(yīng)這一個(gè) Pod 的靜態(tài)庫(kù)。

當(dāng)不想發(fā)布代碼的時(shí)候,也可以使用 Framework 發(fā)布 Pod,CocoaPods 提供了 vendored_framework 選項(xiàng)來使用第三方 Framework。

對(duì)于 Swift 項(xiàng)目,CocoaPods 提供了動(dòng)態(tài) Framework 的支持。通過 use_frameworks! 選項(xiàng)控制。對(duì)于 Swift 寫的庫(kù)來說,想通過 CocoaPods 引入工程,必須加入 use_frameworks! 選項(xiàng)。

關(guān)于 use_frameworks!

在使用CocoaPods的時(shí)候在Podfile里加入use_frameworks! ,那么你在編譯的時(shí)候就會(huì)默認(rèn)幫你生成動(dòng)態(tài)庫(kù),我們能看到每個(gè)源碼Pod都會(huì)在Pods工程下面生成一個(gè)對(duì)應(yīng)的動(dòng)態(tài)庫(kù)Framework的target,我們能在這個(gè)targetBuild Settings -> Mach-O Type看到默認(rèn)設(shè)置是Dynamic Library。也就是會(huì)生成一個(gè)動(dòng)態(tài)Framework,我們能在Products下面看到每一個(gè)Pod對(duì)應(yīng)生成的動(dòng)態(tài)庫(kù)。

這些生成的動(dòng)態(tài)庫(kù)將鏈接到主項(xiàng)目給主工程使用,但是我們上面說過動(dòng)態(tài)庫(kù)需要在主工程target的General -> Embedded Binaries中添加才能使用,而我們并沒有在Embedded Binaries中看到這些動(dòng)態(tài)庫(kù)。那這是怎么回事呢,其實(shí)是cocoapods已經(jīng)執(zhí)行了腳本把這些動(dòng)態(tài)庫(kù)嵌入到了.app的Framework目錄下,相當(dāng)于在Embedded Binaries加入了這些動(dòng)態(tài)庫(kù)。我們能在主工程target的Build Phase -> Embed Pods Frameworks里看到執(zhí)行的腳本。

所以Pod默認(rèn)是生成動(dòng)態(tài)庫(kù),然后嵌入到.app下面的Framework文件夾里。我們?nèi)ods工程的target里把Build Settings -> Mach-O Type設(shè)置為Static Library。那么生成的就是靜態(tài)庫(kù),但是cocoapods也會(huì)把它嵌入到.app的Framework目錄下,而因?yàn)樗庆o態(tài)庫(kù),所以會(huì)報(bào)錯(cuò):unrecognized selector sent to instanceunrecognized selector sent to instance 。

參考

創(chuàng)建一個(gè) iOS Framework 項(xiàng)目
Xcode7創(chuàng)建靜態(tài)庫(kù)和Framework
iOS 靜態(tài)庫(kù)開發(fā)
靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)的使用
iOS 靜態(tài)庫(kù),動(dòng)態(tài)庫(kù)與 Framework
簽名

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

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

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