起因
理論功底
動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)
介紹
靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)的區(qū)別
舉個(gè)例子河绽, iOS 項(xiàng)目中使用 Embeded Framework
靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)如何構(gòu)建和加載
靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)依賴關(guān)系
Xcode 項(xiàng)目結(jié)構(gòu)
iOS 依賴管理事實(shí)上的標(biāo)準(zhǔn)
解決問(wèn)題
制作動(dòng)態(tài)庫(kù)
剖析下動(dòng)態(tài)庫(kù) Framework 吧
回過(guò)頭來(lái)看 Embened Framework
Why Swift does not Support Staic Libraies
CocoaPods 使用 Use_framework!
動(dòng)態(tài)庫(kù) Framework 的文件結(jié)構(gòu)
更愉快的導(dǎo)入文件
資源問(wèn)題
參考
起因
去年,公司iOS端兵多,之前由于所有的業(yè)務(wù)端代碼都是混亂管理疫蔓,造成開(kāi)發(fā)有很多痛點(diǎn)無(wú)法單測(cè)身笤,團(tuán)隊(duì)成員提交代碼沖突機(jī)率大左冬,CI配合效果差,功能性代碼多端無(wú)法復(fù)用范抓,單倉(cāng)庫(kù)代碼量大骄恶,編譯時(shí)間長(zhǎng) 等等痛點(diǎn),領(lǐng)導(dǎo)和組內(nèi)多次溝通開(kāi)始著手組件化開(kāi)發(fā)匕垫,希望能改進(jìn)這些開(kāi)發(fā)中的痛點(diǎn)僧鲁,成立組件化團(tuán)隊(duì)。
組件化的方案大同小異象泵,基礎(chǔ)性代碼封裝私有庫(kù)寞秃,業(yè)務(wù)組件交互交由中間件負(fù)責(zé),項(xiàng)目依賴工具用iOS項(xiàng)目事實(shí)上的標(biāo)準(zhǔn)CocoaPods
前期的基礎(chǔ)性組件拆分都較為順利偶惠,從依賴樹(shù)的葉子節(jié)點(diǎn)開(kāi)發(fā)是最合適的方案春寿。
隨著組件抽離的越來(lái)越多,私有庫(kù)的依賴體系也越來(lái)越復(fù)雜忽孽,慢慢過(guò)渡到了業(yè)務(wù)組件绑改。業(yè)務(wù)組件用了Swift的第三方組件,用了Swift庫(kù)的同學(xué)都知道必須加上use_frameworks!兄一,這個(gè)標(biāo)記是說(shuō)Pod管理的依賴全部編譯為動(dòng)態(tài)庫(kù)厘线,然后呢我們的很多組件又依賴了諸如百度地圖,微信分享等靜態(tài)庫(kù)出革,于是我在執(zhí)行 pod install 報(bào)了一個(gè)沒(méi)有碰見(jiàn)過(guò)的錯(cuò)誤造壮。
1[!]?The?'Pods-LJA_Example'target?has?transitive?dependencies?that?include?static?binaries:
這就尷尬了,于是一陣瘋狂的搜索google stackoverflow等蹋盆,然而并沒(méi)有什么卵用费薄,而且上面催得急,根本沒(méi)時(shí)間處理這些小問(wèn)題 業(yè)務(wù)重構(gòu)是最主要的栖雾,以至于我們的業(yè)務(wù)組件沒(méi)有做到獨(dú)立倉(cāng)庫(kù)拆分楞抡。
直到最近終于找到了解決辦法:( 主要是自己的功力不夠深厚)
理論功底
動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)
介紹
首先靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)都是以二進(jìn)制提供代碼復(fù)用的代碼庫(kù)
靜態(tài)庫(kù) 常見(jiàn)的是 .a
動(dòng)態(tài)庫(kù)常見(jiàn)的是 .dll(windows),.dylib(mac)析藕,so(linux)
framework(in Apple): Framework 是Cocoa/Cocoa Touch程序中使用的一種資源打包方式召廷,可以將代碼文件、頭文件账胧、資源文件竞慢、說(shuō)明文檔等集中在一起,方便開(kāi)發(fā)者使用治泥。也就是說(shuō)我們的 framework其實(shí)是資源打包的方式筹煮,和靜態(tài)庫(kù)動(dòng)態(tài)庫(kù)的本質(zhì)是沒(méi)有關(guān)系的
靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)的區(qū)別
靜態(tài)庫(kù): 鏈接時(shí)會(huì)被完整的復(fù)制到可執(zhí)行文件中,所以如果兩個(gè)程序都用了某個(gè)靜態(tài)庫(kù)居夹,那么每個(gè)二進(jìn)制可執(zhí)行文件里面其實(shí)都含有這份靜態(tài)庫(kù)的代碼
動(dòng)態(tài)庫(kù): 鏈接時(shí)不復(fù)制败潦,在程序啟動(dòng)后用dyld加載本冲,然后再?zèng)Q議符號(hào),所以理論上動(dòng)態(tài)庫(kù)只用存在一份劫扒,好多個(gè)程序都可以動(dòng)態(tài)鏈接到這個(gè)動(dòng)態(tài)庫(kù)上面檬洞,達(dá)到了節(jié)省內(nèi)存(不是磁盤是內(nèi)存中只有一份動(dòng)態(tài)庫(kù)),還有另外一個(gè)好處沟饥,由于動(dòng)態(tài)庫(kù)并不綁定到可執(zhí)行程序上添怔,所以我們想升級(jí)這個(gè)動(dòng)態(tài)庫(kù)就很容易,windows和linux上面一般插件和模塊機(jī)制都是這樣實(shí)現(xiàn)的贤旷。
But我們的蘋果爸爸在iOS平臺(tái)上規(guī)定不允許存在動(dòng)態(tài)庫(kù)广料,并且所有的 IPA 都需要經(jīng)過(guò)蘋果爸爸的私鑰加密后才能用,基本你用了動(dòng)態(tài)庫(kù)也會(huì)因?yàn)楹灻粚?duì)無(wú)法加載遮晚,(越獄和非 APP store 除外)性昭。于是就把開(kāi)發(fā)者自己開(kāi)發(fā)動(dòng)態(tài)庫(kù)掐死在幻想中。
直到有一天县遣,蘋果爸爸的iOS升級(jí)到了8,iOS出現(xiàn)了APP Extension汹族,swift編程語(yǔ)言也誕生了萧求,由于iOS主APP需要和Extension共享代碼,Swift語(yǔ)言的機(jī)制也只能有動(dòng)態(tài)庫(kù)顶瞒,于是蘋果爸爸尷尬了夸政,不過(guò)這難不倒我們的蘋果爸爸,畢竟我是爸爸榴徐,規(guī)則是我來(lái)定守问,我想怎樣就怎樣,于是提出了一個(gè)概念Embedded Framework坑资,這種動(dòng)態(tài)庫(kù)允許APP和APP Extension共享代碼耗帕,但是這份動(dòng)態(tài)庫(kù)的生命被限定在一個(gè)APP進(jìn)程內(nèi)。簡(jiǎn)單點(diǎn)可以理解為被閹割的動(dòng)態(tài)庫(kù)袱贮。
舉個(gè)例子仿便,iOS項(xiàng)目中使用Embeded Framework
如果你把某個(gè)自己開(kāi)發(fā)的動(dòng)態(tài)庫(kù)(系統(tǒng)的不算,畢竟蘋果是爸爸)放在了Linked Frameworks and Libraries里面攒巍,程序一啟動(dòng)就會(huì)報(bào)Reason: Image Not Found嗽仪,你只能把它放在Embeded Binaries里面才能正常使用,
看圖:?
靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)如何構(gòu)建和加載
簡(jiǎn)單點(diǎn)块仆,說(shuō)話的方式簡(jiǎn)單點(diǎn)~~
上面的介紹貌似有點(diǎn)抽象啊套用在美團(tuán)技術(shù)分享大會(huì)上的話就是:
靜態(tài)庫(kù): 一堆目標(biāo)文件(.o/.obj)的打包體(并非二進(jìn)制文件)
動(dòng)態(tài)庫(kù): 一個(gè)沒(méi)有main函數(shù)的可執(zhí)行文件
這里我們來(lái)復(fù)習(xí)下C語(yǔ)言的基本功默辨,編譯和鏈接
編譯: 將我們的源代碼文件編譯為目標(biāo)文件
鏈接: 將我們的各種目標(biāo)文件加上一些第三方庫(kù)棚辽,和系統(tǒng)庫(kù)鏈接為可執(zhí)行文件。
由于某個(gè)目標(biāo)文件的符號(hào)(可以理解為變量窿凤,函數(shù)等)可能來(lái)自其他目標(biāo)文件搀擂,其實(shí)鏈接這一步最主要的操作就是決議符號(hào)的地址。
若符號(hào)來(lái)自靜態(tài)庫(kù)(本質(zhì)就是.o 的集合包)或 .o卷玉,將其納入鏈接產(chǎn)物哨颂,并確定符號(hào)地址
若符號(hào)來(lái)自動(dòng)態(tài)庫(kù),打個(gè)標(biāo)記相种,等啟動(dòng)的時(shí)候再說(shuō)---交給dyld去加載和鏈接符號(hào)
于是鏈接加裝載就有了不同的情況
Load 裝載:將庫(kù)文件載入內(nèi)存
? ? Static Loading:?jiǎn)?dòng)時(shí)
? ? Dynamic Loading:?jiǎn)?dòng)后(使用時(shí))
Link 鏈接:決議符號(hào)地址
? ? Static Linking:構(gòu)建(鏈接)時(shí)
? ? Dynamic Linking:運(yùn)行時(shí)(啟動(dòng)時(shí)或使用時(shí))
然后組合起來(lái)就是 2 * 2 = 4 了
Static Loading + Static Linking
Static Loading + Dynamic Linking
Dynamic Loading + Dynamic Linking
~~Dynamic Loading + Static Linking~~
第一種是純靜態(tài)庫(kù)相關(guān)了
第二種就是靜態(tài)加載(啟動(dòng)時(shí))威恼,動(dòng)態(tài)鏈接,鏈接時(shí)寝并,動(dòng)態(tài)庫(kù)參與鏈接箫措,但是這時(shí)候只是給符號(hào)打了標(biāo)記告訴我這個(gè)符號(hào)來(lái)自與動(dòng)態(tài)庫(kù),程序啟動(dòng)時(shí)衬潦,iOS或者M(jìn)ac OS操作系統(tǒng)的dyld自動(dòng)load + link斤蔓。
既然全部都是自動(dòng)的。那么符號(hào)的調(diào)用方完全不知道你到底是源碼還是靜態(tài)庫(kù)镀岛,動(dòng)態(tài)庫(kù) 弦牡。
第三種收到調(diào)用dlopen + performSelector通常iOS的APP不適用這里不討論
第四種,沒(méi)見(jiàn)過(guò)漂羊,個(gè)人也不是特別懂
有需求請(qǐng)參看文后的程序員的自我修養(yǎng)一書
靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)依賴關(guān)系
既然有 2 種庫(kù)驾锰,那么依賴關(guān)系又是 2 * 2 嘍
libA.a dependency libB.a
UIKit.dylib dependency Foundation.dylib
libA.a dependency Foundation.dylib
MyXX.dylib dependency libA.a
第一種 靜態(tài)庫(kù)互相依賴,這種情況非常常見(jiàn)走越,制作靜態(tài)庫(kù)的時(shí)候只需要有被依賴的靜態(tài)庫(kù)頭文件在就能編譯出來(lái)椭豫。但是這就意味者你要收到告訴使用者你的依賴關(guān)系
幸運(yùn)的是CocoaPod就是這樣做的
第二種動(dòng)態(tài)庫(kù)依賴動(dòng)態(tài)庫(kù),兩個(gè)動(dòng)態(tài)庫(kù)是相互隔離的具有隔離性旨指,但是制作的靜態(tài)庫(kù)的時(shí)候需要被依賴動(dòng)態(tài)庫(kù)參與鏈接赏酥,但是具體的符號(hào)決議交給dyld來(lái)做。
第三種谆构,靜態(tài)庫(kù)依賴動(dòng)態(tài)庫(kù)裸扶,也很常見(jiàn),靜態(tài)庫(kù)制作的時(shí)候也需要?jiǎng)討B(tài)庫(kù)參與鏈接低淡,但是符號(hào)的決議交給dyld來(lái)做姓言。
第四種,動(dòng)態(tài)庫(kù)依賴靜態(tài)庫(kù)蔗蹋,這種情況就有點(diǎn)特殊了何荚。首先我們?cè)O(shè)想動(dòng)態(tài)庫(kù)編譯的時(shí)候需要靜態(tài)庫(kù)參與編譯,但是靜態(tài)庫(kù)交由dyld來(lái)做符號(hào)決議猪杭,but這和我們前面說(shuō)的就矛盾了啊餐塘。靜態(tài)庫(kù)本質(zhì)是一堆.o 的打包體,首先并不是二進(jìn)制可執(zhí)行文件皂吮,再者你無(wú)法保證主程序把靜態(tài)庫(kù)參與鏈接共同生成二進(jìn)制可執(zhí)行文件戒傻。這就尷尬了税手。
怎么辦?
目前的編譯器的解決辦法是需纳,首先我無(wú)法保證主程序是否包含靜態(tài)庫(kù)芦倒,再者靜態(tài)庫(kù)也無(wú)法被dyld加載,那么我直接把你靜態(tài)庫(kù)的.o 偷過(guò)來(lái)不翩,共同組成一個(gè)新的二進(jìn)制兵扬。也被稱做吸附性
那么我有多份動(dòng)態(tài)庫(kù)都依賴同樣的靜態(tài)庫(kù),這就尷尬了口蝠,每個(gè)動(dòng)態(tài)庫(kù)為了保證自己的正確性會(huì)把靜態(tài)庫(kù)吸附進(jìn)來(lái)器钟。然后兩個(gè)庫(kù)包含了同樣的靜態(tài)庫(kù),于是問(wèn)題就出現(xiàn)了妙蔗。 看到這里想必前面出現(xiàn)的錯(cuò)誤你已經(jīng)能猜出來(lái)了把~_~
后面再詳細(xì)解釋
先來(lái)個(gè)總結(jié)
可執(zhí)文件(主程序或者動(dòng)態(tài)庫(kù))在構(gòu)建的鏈接階段
遇到靜態(tài)庫(kù)傲霸,吸附進(jìn)來(lái)
遇到動(dòng)態(tài)庫(kù),打標(biāo)記眉反,彼此保持獨(dú)
Xcode 項(xiàng)目結(jié)構(gòu)
target:對(duì)于一個(gè)產(chǎn)物(app,.a ,.framework)
project:一個(gè)項(xiàng)目包含多個(gè) target
workspace: 一個(gè)包含多個(gè) target
schema: 指定了一個(gè)產(chǎn)物是按照何種的依賴關(guān)系昙啄,編譯-鏈接到最終的一個(gè)產(chǎn)物
iOS 依賴管理事實(shí)上的標(biāo)準(zhǔn)
這么多年,Apple的博客和文檔也就告訴了我們什么是靜態(tài)庫(kù)什么是動(dòng)態(tài)庫(kù)禁漓,如何制作等跟衅。但是并沒(méi)有給我們提供一系列的依賴管理工具。所以CocoaPods成了事實(shí)上的標(biāo)準(zhǔn)播歼。
通常CocoaPods管理的工程結(jié)構(gòu)如下:
那么當(dāng)我們按下CMD + B的時(shí)候,整個(gè)項(xiàng)目按照先編譯被依賴Pod掰读,然后依賴其他Pod的Pod也被構(gòu)建出來(lái)秘狞,最終所有的組件被編譯為一個(gè)lib-Pods-XXXAPP.a被添加進(jìn)項(xiàng)目進(jìn)去。資源通過(guò)CocoaPods提供的腳本也一并被復(fù)制進(jìn)去蹈集。想了解CocoaPods做了什么的讀者可以參看后面的鏈接
解決問(wèn)題
這么多理論功底的建立烁试,相信我們已經(jīng)能分析出來(lái)之前pod install的原因了。就是用了use_framework那么我們的所有Pod都會(huì)以動(dòng)態(tài)庫(kù)(Embeded Framework)的形式去構(gòu)建拢肆,于是那些非開(kāi)源的庫(kù)(如百度地圖减响,微信分享)如果被多個(gè)Pod依賴(組件化開(kāi)發(fā)中太常見(jiàn)了)于是被吸附到動(dòng)態(tài)庫(kù)里面,所以CocoaPod直接就不讓我們install成功郭怪。因?yàn)槟悻F(xiàn)在的依賴管理就是錯(cuò)誤的支示。
在聽(tīng)取美團(tuán)葉樉老師分享的時(shí)候 他們的出發(fā)點(diǎn)是因?yàn)橐@過(guò)蘋果爸爸在iOS9以下對(duì)__text 段60M的限制使用了動(dòng)態(tài)庫(kù)方案,我們是因?yàn)槟承﹕wift庫(kù)必須要用到(歷史遺留原因)動(dòng)態(tài)庫(kù)鄙才。美團(tuán)的做法是摘除依賴關(guān)系颂鸿,自定義CocoaPods(開(kāi)源的本來(lái)就是用著不爽我就改)。但是我是個(gè)小菜雞啊攒庵。我也不會(huì) ruby(以后會(huì)學(xué)的)嘴纺,但是葉樉老師給我提了別的idea败晴。前面我們知道 動(dòng)態(tài)庫(kù)和動(dòng)態(tài)庫(kù)是隔離性,動(dòng)態(tài)庫(kù)依賴靜態(tài)庫(kù)具有吸附性栽渴,那么我們可以自定義一個(gè)動(dòng)態(tài)庫(kù)把百度地圖這種靜態(tài)庫(kù)吸附進(jìn)來(lái)尖坤。對(duì)外整體呈現(xiàn)的是動(dòng)態(tài)庫(kù)特性。其他的組件依賴我們自定義的動(dòng)態(tài)庫(kù)闲擦,由于隔離性的存在慢味,不會(huì)出現(xiàn)問(wèn)題。
制作動(dòng)態(tài)庫(kù)
1 創(chuàng)建動(dòng)態(tài)庫(kù)項(xiàng)目這里以 wx 舉例
2 按照微信的官方文檔佛致。添加依賴庫(kù)(我是因?yàn)閜od install巨慢所以我直接拽進(jìn)來(lái)了)
3 將wx的PublicHeader暴露出來(lái)贮缕,注意由于我并沒(méi)有使用到wx相關(guān)API所以鏈接器幫我們鏈接動(dòng)態(tài)庫(kù)的時(shí)候可能并不會(huì)把wx靜態(tài)庫(kù)吸附進(jìn)來(lái)。我們手動(dòng)在build Setting的other link flags加上-all_load標(biāo)記
4.在Schema里面跳轉(zhuǎn)編譯配置為Release俺榆,并且選擇所有的CPU架構(gòu)
5 然后選擇模擬器或者 Generic iOS Device 運(yùn)行編譯就會(huì)生成對(duì)應(yīng)版本的 Framework 了感昼。
6.但是為了保證開(kāi)發(fā)者使用的時(shí)候是真機(jī)模擬器都能正常使用,我們需要合并不同架構(gòu)
這里在Build Phases里添加以下腳本罐脊,真機(jī)和模擬器都Build一遍之后就會(huì)在工程目錄下生成Products文件夾定嗓,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
于是我們有了我們自己的私有動(dòng)態(tài)庫(kù)LJWXSDK,那么我們來(lái)驗(yàn)證我們之前的問(wèn)題
首先指定一個(gè)LJWXSDK.podspec這里我直接傳到了我的Github上面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#
#?Be?sure?to?run?`pod?lib?lint?LJPod.podspec'?to?ensure?this?is?a
#?valid?spec?before?submitting.
#
#?Any?lines?starting?with?a?#?are?optional,?but?their?use?is?encouraged
#?To?learn?more?about?a?Podspec?seehttp://guides.cocoapods.org/syntax/podspec.html
#
Pod::?Spec.newdo|s|
??s.name?????????????=?'LJWXSDK'
??s.version??????????=?'0.1.0'
??s.summary??????????=?'A?short?description?of?LJWXSDK.'
??s.description??????=?<?'MIT',?:?file?=>?'LICENSE'}
??s.author???????????=?{?'ValiantCat'=>?'519224747@qq.com'}
??s.source?=?{?:?http??=>?'http://onk2m6gtu.bkt.clouddn.com/LJWXSDK.framework.zip'}
??s.ios.deployment_target?=?'8.0'
??s.default_subspec?=?'zip'
??s.subspec?'zip'do|zip|
????puts?'-------------------------------------------------------------------'
????puts?'Notice:?LJWXSDK?is?zip?now'
????puts?'-------------------------------------------------------------------'
????zip.ios.vendored_frameworks?=?'*.framework'
??end
end
注意上面我是把二進(jìn)制壓縮丟進(jìn)了七牛的 oss 文件存儲(chǔ)萍桌。畢竟免費(fèi)還快宵溅。
然后通過(guò) pod lib create 創(chuàng)建了一個(gè) pod 用來(lái)驗(yàn)證之前我們的傳遞性依賴問(wèn)題,
文件夾結(jié)構(gòu)如下
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
.
├──?Example
│???├──?LJA
│???│???├──?Base.lproj
│???│???│???├──?LaunchScreen.storyboard
│???│???│???└──?Main.storyboard
│???│???├──?Images.xcassets
│???│???│???└──?AppIcon.appiconset
│???│???│???????└──?Contents.json
│???│???├──?LJA-Info.plist
│???│???├──?LJA-Prefix.pch
│???│???├──?LJAppDelegate.h
│???│???├──?LJAppDelegate.m
│???│???├──?LJViewController.h
│???│???├──?LJViewController.m
│???│???├──?en.lproj
│???│???│???└──?InfoPlist.strings
│???│???└──?main.m
│???├──?LJA.xcodeproj
│???├──?LJA.xcworkspace
│???├──?Podfile
│???├──?Podfile.lock
│???├──?Pods
│???│???├──?Headers
│???│???├──?LJWXSDK
│???│???│???└──?LJWXSDK.framework
│???│???│???????├──?Headers
│???│???│???????│???├──?LJWXSDK.h
│???│???│???????│???├──?WXApi.h
│???│???│???????│???├──?WXApiObject.h
│???│???│???????│???└──?WechatAuthSDK.h
│???│???│???????├──?Info.plist
│???│???│???????├──?LJWXSDK
│???│???│???????├──?Modules
│???│???│???????│???└──?module.modulemap
│???│???│???????├──?_CodeSignature
│???│???│???????│???└──?CodeResources
│???│???│???????└──?read_me.txt
│???│???├──?Local\?Podspecs
│???│???│???├──?LJA.podspec.json
│???│???│???├──?LJB.podspec.json
│???│???│???└──?LJWXSDK.podspec.json
│???│???├──?Manifest.lock
│???│???├──?Pods.xcodeproj
│???│???│???├──?project.pbxproj
│???│???│???├──?project.xcworkspace
│???│???├──?Target\?Support\?Files
│???│???│???├──?LJA
│???│???│???│???├──?Info.plist
│???│???│???│???├──?LJA-dummy.m
│???│???│???│???├──?LJA-prefix.pch
│???│???│???│???├──?LJA-umbrella.h
│???│???│???│???├──?LJA.modulemap
│???│???│???│???└──?LJA.xcconfig
│???│???│???├──?LJB
│???│???│???│???├──?Info.plist
│???│???│???│???├──?LJB-dummy.m
│???│???│???│???├──?LJB-prefix.pch
│???│???│???│???├──?LJB-umbrella.h
│???│???│???│???├──?LJB.modulemap
│???│???│???│???└──?LJB.xcconfig
│???│???│???├──?Pods-LJA_Example
│???│???│???│???├──?Info.plist
│???│???│???│???├──?Pods-LJA_Example-acknowledgements.markdown
│???│???│???│???├──?Pods-LJA_Example-acknowledgements.plist
│???│???│???│???├──?Pods-LJA_Example-dummy.m
│???│???│???│???├──?Pods-LJA_Example-frameworks.sh
│???│???│???│???├──?Pods-LJA_Example-resources.sh
│???│???│???│???├──?Pods-LJA_Example-umbrella.h
│???│???│???│???├──?Pods-LJA_Example.debug.xcconfig
│???│???│???│???├──?Pods-LJA_Example.modulemap
│???│???│???│???└──?Pods-LJA_Example.release.xcconfig
│???│???│???└──?Pods-LJA_Tests
│???│???│???????├──?Info.plist
│???│???│???????├──?Pods-LJA_Tests-acknowledgements.markdown
│???│???│???????├──?Pods-LJA_Tests-acknowledgements.plist
│???│???│???????├──?Pods-LJA_Tests-dummy.m
│???│???│???????├──?Pods-LJA_Tests-frameworks.sh
│???│???│???????├──?Pods-LJA_Tests-resources.sh
│???│???│???????├──?Pods-LJA_Tests-umbrella.h
│???│???│???????├──?Pods-LJA_Tests.debug.xcconfig
│???│???│???????├──?Pods-LJA_Tests.modulemap
│???│???│???????└──?Pods-LJA_Tests.release.xcconfig
│???│???└──?libWeChatSDK
│???│???????├──?README.md
│???│???????├──?WXApi.h
│???│???????├──?WXApiObject.h
│???│???????├──?WechatAuthSDK.h
│???│???????└──?libWeChatSDK.a
├──?LICENSE
├──?LJA
│???├──?Assets
│???└──?Classes
│???????└──?LJA.m
├──?LJA.podspec
├──?LJB
│???├──?Assets
│???└──?Classes
│???????└──?LJB.m
├──?LJB.podspec
├──?README.md
└──?_Pods.xcodeproj?->?Example/Pods/Pods.xcodeproj
測(cè)試工程我也丟在7牛上面上炎。下載測(cè)試即可
編譯運(yùn)行恃逻。完美。我們又可以愉快的和swift第三方庫(kù)配合使用藕施。
很多人可能會(huì)問(wèn) 諸如百度地圖 微信這種sdk為什么官方不支持動(dòng)態(tài)庫(kù)版(所說(shuō)的都是embeded Framework)寇损,猜測(cè)是為了兼容更低iOS7版本吧
很多人會(huì)覺(jué)得麻煩的要死。首先每個(gè)公司多多少少都有歷史包袱裳食,麻煩也要做矛市,再者這是一次對(duì)基本功的補(bǔ)充,即便你們沒(méi)有用到诲祸,但是為了學(xué)習(xí)浊吏,這篇教程所做的也值得你嘗試一次。
剖析下動(dòng)態(tài)庫(kù) Framework 吧
上述解決了我們一開(kāi)始遇到的問(wèn)題救氯。but既然動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)壓根就不一回事找田,所以里面還是有很多細(xì)節(jié)值得我們?nèi)チ私獾摹?/p>
回過(guò)頭來(lái)看 Embened Framework
首先我們之前記得如果一個(gè)動(dòng)態(tài)庫(kù)加在LinkedFrameworksand Libraies程序啟動(dòng)就會(huì)報(bào)ImageNotFound,如果放在EmbededBinaries里面就可以径密。這是為什么呢午阵。我們拿MacoView來(lái)看下兩種情況下可執(zhí)行文件的細(xì)節(jié)
其中@rpth 這個(gè)路徑表示的位置可以查看Xcode中的鏈接路徑問(wèn)題
這樣我們就知道了其實(shí)加在EmbededBinaries里面的東西其實(shí)會(huì)被復(fù)制一份到xx.app里面,所以這個(gè)名字起得還是不錯(cuò)的直譯就是嵌入的框架
Why Swift does not Support Staic Libraies
造成這個(gè)的主要原因是Swift的運(yùn)行時(shí)庫(kù)(不等同于OC的runtime概念),由于Swift的ABI不穩(wěn)定底桂,靜態(tài)庫(kù)會(huì)導(dǎo)致最終的目標(biāo)程序中包含重復(fù)的運(yùn)行庫(kù)植袍,相關(guān)可以看下最后的參考文章SwiftInFlux#static-libraries。等到我們的SwiftABI穩(wěn)定之后籽懦,我們的靜態(tài)庫(kù)支持可能就又會(huì)出現(xiàn)了于个。當(dāng)然也可能不出Swift伴隨誕生的SPM(Swift,Package Manager)暮顺,可能有更好的官方的包依賴管理工具厅篓。讓我們期待吧。
CocoaPods使用Use_framework!
既然加了Swift的第三方庫(kù)之后就需要在Podfile里面加上use_framework! 那么CocoaPods就會(huì)幫我們生成動(dòng)態(tài)庫(kù)捶码,但是奇怪的是羽氮,我們并沒(méi)有在主工程的embeded binaries看到這個(gè)動(dòng)態(tài)庫(kù),這又是什么鬼惫恼。其實(shí)是CocoaPods使用腳本幫我們加進(jìn)去了档押。腳本位置在主工程的build Phase下的Emded Pods frameworks
1"${SRCROOT}/Pods/Target?Support?Files/Pods-LJA_Example/Pods-LJA_Example-frameworks.sh"
動(dòng)態(tài)庫(kù)Framework的文件結(jié)構(gòu)
1
2
3
4
5
6
7
8
9
10
11
12
.
├──?Headers
│???├──?LJWXSDK.h
│???├──?WXApi.h
│???├──?WXApiObject.h
│???└──?WechatAuthSDK.h
├──?Info.plist
├──?LJWXSDK
├──?Modules
│???└──?module.modulemap
└──?_CodeSignature
????└──?CodeResources
Headers 一般是頭文件。非private里面的頭文件都會(huì)在里面
info.plist 配置信息祈纯,不深究
Modules 這個(gè)文件夾里有個(gè)module.modulemap文件令宿,后面在講解
二進(jìn)制文件,這就是上面提到的不帶main的二進(jìn)制文件了腕窥,.o 的打包體
_codeSignature簽名文件 (蘋果爸爸的約束)
more資源文件粒没。這里暫時(shí)沒(méi)用到,所以沒(méi)有 簇爆,但是這個(gè)也是個(gè)大坑
更愉快的導(dǎo)入文件
@class癞松,@protocol:不說(shuō)了就是聲明一個(gè)類,并不導(dǎo)入入蛆。
#import <>, #import"":是加強(qiáng)版的#include<>拦惋,#include"" 防止重復(fù)導(dǎo)入的。
#import<> : 通過(guò)build setting里面中的header Search Path里面去找
#import"" : 第一步先搜索user Header search Path再搜索 header search Path 安寺。所以對(duì)我們的framework來(lái)說(shuō),CocoaPod幫我們加到了Header search Path目前2種導(dǎo)入方式都是可以支持的首尼。
上面的導(dǎo)入方式都帶了 某個(gè)framework的路徑"xx/xx.h" 挑庶,我們?cè)陂_(kāi)發(fā)自己主工程的時(shí)候會(huì)發(fā)現(xiàn)我們導(dǎo)入主工程其他類是不需要導(dǎo)入前綴的。 這又是怎么回事软能。
看下面的配置
目前的配置是non-recursive迎捺。如果把non去掉意思就是我可以遞歸的去查找某些framework下面的頭文件了。 但是Xcode的效率肯定就會(huì)有影響查排。
還是不建議修改的好凳枝。
大家都知道iOS7之后多了@import,這又是什么鬼。
簡(jiǎn)單理解這個(gè)方式叫做Module導(dǎo)入岖瑰,好處就是使用了@import 之后不需要在project setting手動(dòng)添加framework叛买,系統(tǒng)會(huì)自動(dòng)加載,而且效率更高蹋订。
最主要的是swift也只能這樣用率挣。
導(dǎo)入的時(shí)候系統(tǒng)會(huì)查找如果有模塊同名的文件就會(huì)導(dǎo)入這個(gè)文件。如果沒(méi)有CocoaPods幫我們生成一個(gè)module-umbrela.hl文件露戒,然后就是導(dǎo)入的這個(gè)文件椒功。
回過(guò)頭來(lái)看我們的framework的結(jié)構(gòu) 里面有個(gè)Modules文件夾,里面有個(gè)文件module.modulemap
1
2
3
4
5
6
framework?module?LJWXSDK?{
??umbrella?header?"LJWXSDK.h"
??export?*
??module?*?{?export?*?}
}
我們可以看到其實(shí)被暴露的header就是這個(gè)文件智什,之前我在按照#import "/"的時(shí)候有個(gè)警告
而且按照@import 導(dǎo)入的東西發(fā)現(xiàn)沒(méi)有導(dǎo)入可用的頭文件就是因?yàn)椴](méi)有在umbrella header的頭文件中加入其他頭文件动漾。
加入之后我們就可以完美的使用@import ,并且#import"/" 也不會(huì)報(bào)warning
更多關(guān)于umbrella Header 參看文后參考
資源問(wèn)題
首先我們來(lái)看常見(jiàn)的資源文件: 主要分為圖片和其他類資源那么加載圖片和加載其他資源都是怎么做的荠锭?
1: [UIimage imageNamed:]
2: [NSbundle bundleForclass[XXX class]]
其實(shí)方式1去本質(zhì)就是去mainBundle去拿資源旱眯,方式2從XXX所在的框架里面去拿。
前面也說(shuō)道framework只是資源的打包方式节沦,本質(zhì)上是有兩種的键思。
我們這個(gè)framework如果本質(zhì)是靜態(tài)庫(kù),那么無(wú)需改變使用方式甫贯,資源最終都會(huì)打包到Main Bundle里面
如果我們這個(gè)framework本質(zhì)是動(dòng)態(tài)庫(kù)吼鳞,那么我們的資源就發(fā)生了變化,資源就會(huì)被存放在framework里面叫搁。所以我們需要使[NSbundle bundleForclass[XXX class]]赔桌。需要注意的是很多人為了簡(jiǎn)單,下意
的使用self class傳遞渴逻,但是有可能這個(gè)self實(shí)例不在資源所屬的framework疾党。所以會(huì)出現(xiàn)資源加載失敗。一定要謹(jǐn)慎使用惨奕。
抄自:?http://www.cocoachina.com/ios/20170427/19136.html
參考
iOS 里的動(dòng)態(tài)庫(kù)和靜態(tài)庫(kù)
Systems Programming: What is the exact difference between Dynamic loading and dynamic linking?
Dynamic Linking of Imported Functions in Mach-O
iOS - Umbrella Header 在 framework 中的應(yīng)用