常見的庫文件格式
.a : 靜態(tài)庫
.dylib : 傳統(tǒng)意義動(dòng)態(tài)庫
.framework : 可動(dòng)可靜
.xcframework: 針對不同架構(gòu)的搞一起次绘。
xcframework,18年蘋果出的新的庫的格式瘪阁,我們平常開發(fā)中對胖庫各種架構(gòu)都支持的sdk ,上線的時(shí)候需要把比如模擬器的架構(gòu)剔除邮偎,為了節(jié)省空間管跺,可是當(dāng)想用模擬器運(yùn)行的時(shí)候又會(huì)發(fā)現(xiàn)此架構(gòu)被剔除了,所以這種庫的出現(xiàn)會(huì)省去此操作禾进,想要鏈接什么架構(gòu)就鏈接什么架構(gòu)豁跑。
這篇文章主要介紹 .a 和.framework
什么時(shí)候會(huì)用到庫
1、某些代碼需要給別人使用泻云,但是我們不希望別人看到源碼艇拍,就需要以庫的形式進(jìn)行封裝狐蜕,只暴露出頭文件。
2卸夕、對于某些不會(huì)進(jìn)行大改動(dòng)的代碼层释,我們想減少編譯時(shí)間,就可以把它打包成庫快集,因?yàn)閹焓且呀?jīng)編譯好的二進(jìn)制贡羔,編譯的時(shí)候只需要link一下,不會(huì)浪費(fèi)編譯時(shí)間个初。
操作準(zhǔn)備
先看一份代碼
#import <Foundation/Foundation.h>
#import <AFNetworking.h>
int main(){
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSLog(@"testApp----%@", manager);
return 0;
}
- 上面代碼就是一個(gè)簡單的test.m文件乖寒。
- 可以看到 此代碼引用了 AFNetworking 并用到了里面的一個(gè)方法,這樣就構(gòu)成了一個(gè)場景院溺,我們的主工程用到了一個(gè)第三方庫楣嘁。
在看一下這個(gè)目錄
- 這里可以看到我們引用的是一個(gè)AFNetworking的靜態(tài)庫代碼(因?yàn)槲覀兛吹搅?a)
- 還有它的頭文件
file 命令去查看一下它具體的格式
lipo -info 可以去查看它支持的一個(gè)架構(gòu)
此時(shí) 就給我們鏈接一個(gè)靜態(tài)庫需要的所有信息。
那這個(gè)靜態(tài)庫.a 到底是什么覆获?是什么格式马澈?
上面 通過 file 命令我們知道它是一個(gè) archive 文檔格式的,
我們還知道它是一個(gè).o 文件的合集 那么我們可以通過 ar命令 來查看一下
- 由此 可以驗(yàn)證出 .a 它就是一個(gè).o文件的合集
正式鏈接
上邊我們已經(jīng)準(zhǔn)備好調(diào)節(jié)弄息,下面我們就正式的將我們的.m與AFN進(jìn)行一個(gè)鏈接
- 首先我們知道 編譯的一個(gè)過程是將.m 生成目標(biāo)文件.o 之后再進(jìn)行一個(gè)鏈接.最后生成可執(zhí)行文件痊班,或者 動(dòng)態(tài)庫。
1摹量、將test .m文件編譯為目標(biāo)文件
這里就需要借助我們平常熟悉的東西 Clang 大家都知道锚赤,clang是前端編譯器。那么我們看一下它的自描述
- clang 是一個(gè) c 消返、c++ 和 oc 的編譯器
- clang是一個(gè)C, c++和Objective-C編譯器郁岩,包含了 預(yù)
處理、解析睦尽、優(yōu)化器净、代碼生成、匯編和鏈接当凡。
根據(jù)傳入的高級模式設(shè)置山害,Clang將停止
在做一個(gè)完整的鏈接之前 - clang可執(zhí)行文件實(shí)際上是一個(gè)小的驅(qū)動(dòng)程序,它控制其他程序的整體執(zhí)行
諸如編譯器沿量、匯編器和鏈接器等工具浪慌。通常,您不需要與
驅(qū)動(dòng)程序朴则,但您可以使用它來運(yùn)行其他工具权纤。
2.開始編譯為.o
clang -x objective-c \
-target x86_64-apple-macos11.0\
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-I ./AFNetworking \
-c test.m -o test.o
- clang -x :來指定編譯的語言.
- -target : 指定編譯的架構(gòu)
- -fobjc-arc : 指定為 arc環(huán)境
- -isysroot : 這就是用到的系統(tǒng)庫所在的位置 (例如我們.m 里面引用了<Foundation/Foundation.h>)
- -I :指定里面引用的第三方庫頭文件的路徑 header serach path
- -c :輸出
- \ 換行輸入 為了好看
通過上面一系列操作 遍輸出了.o文件
- 在上篇文章我們有講過.o 里邊有重定位符號表,重定位符號里保存的是 當(dāng)前用到的符號. 那它的作用是什么?其實(shí)就是當(dāng)我們進(jìn)行鏈接的時(shí)候汹想,通過這個(gè)重定位符號表再次重定位外邓,生成具體的一個(gè)符號信息。
- 這也就是為什么我們生成目標(biāo)文件只需要一個(gè)頭文件的地址就可以了古掏,因?yàn)樵偕赡繕?biāo)文件的時(shí)候坐榆,只需要告訴clang 哪個(gè)地方需要進(jìn)行重定位就好了。
- 進(jìn)行鏈接 靜態(tài)庫 生成可執(zhí)行文件
上面我們已經(jīng)完成了目標(biāo)文件的生成冗茸,下面我們通過連接器生成可執(zhí)行文件席镀,同樣的我們知道我們的clang也是包含我們鏈接器的一個(gè)接口的。
$ clang -target x86_64-apple-macos11.0 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-L./AFNetworking \
-lAFNetworking \
test.o -o test
ld: in ./AFNetworking/libAFNetworking.a(AFHTTPSessionManager.o), building for macOS, but linking in object file built for iOS Simulator, for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
- -target :指定架構(gòu)
- -fobjc-arc:指定 為arc
- -isysroot : 系統(tǒng)庫所在的位置 如NSLog
- -L :第三方靜態(tài)庫的位置(這里是libAFNetworking.a的地址夏漱,向我們xcode Library Search Paths)
鏈接的過程就需要把重定位符號表的符號進(jìn)行重定位豪诲,也就是說需要符號的真實(shí)的地址,向我們用到的AFHTTPSessionManager 它真實(shí)的地址在哪? 是不是保存在靜態(tài)庫.a 里面的.o文件里去了挂绰,也就是在.a的重定位符號表里屎篱, 我們生成的.o也有重定位符號表,當(dāng)生成可執(zhí)行文件的時(shí)候會(huì)將其融合生成一個(gè)完整的符號表葵蒂。因?yàn)樵诳蓤?zhí)行文件中只有一個(gè)符號表交播。
- -l:上面我們告訴了連接器我的靜態(tài)庫放在哪里那接下來就需要告訴它我連接哪個(gè)庫文件(注意:這里說的是庫文件并沒有說靜態(tài)庫) 這里是提供了一個(gè) -l 參數(shù) 這里我們寫-lAFNetworking
那這里為什么這么寫-lAFNetworking ?因?yàn)樗怯幸粋€(gè)查找規(guī)則的践付,它會(huì)先查找lib+<library_name>的動(dòng)態(tài)庫秦士,找不到再去找lib+<library_name>的靜態(tài)庫 ,在找不到就會(huì)報(bào)錯(cuò)永高。
- test.o -o test : 輸入為可執(zhí)行文件隧土。
上面為啥報(bào)錯(cuò) 因?yàn)槭茿FNetworking 架構(gòu)的問題,它是模擬器架構(gòu)命爬,我們這是電腦環(huán)境曹傀。
重要
通過上面的操作我們知道了:鏈接成功一個(gè)庫的三要素:
1、-l<directory> (大i)指定目錄尋找頭文件
xcode: Header Search Paths
2饲宛、-L<dir> 指定庫文件路徑
xcode: :Library Search Paths
3皆愉、-l<library_name>(小L)指定鏈接庫文件名稱(.a.dylib庫文件)
xcode: Other link flags 配置的 如-lAFNetworking
靜態(tài)庫的合并
我們知道靜態(tài)庫是.o文件的合集,那么我們是不是可以將多個(gè).a文件合并為一個(gè).a當(dāng)然是可以的
看一下
可以通過 ar命令 來進(jìn)行 操作
ar
- ar——?jiǎng)?chuàng)建和維護(hù)library 文檔格式艇抠。
- ar實(shí)用程序創(chuàng)建并維護(hù)合并到存檔中的文件組幕庐。一旦創(chuàng)建了存檔,就可以添加和刪除新文件
可以提取练链、刪除或替換現(xiàn)有文件翔脱。
隨意找兩個(gè).a 我們進(jìn)行一個(gè)合并 下面我就搞倆.a
首先查看一下 下面兩個(gè).a 中的內(nèi)容
- 由此可見的確是.o的一個(gè)合集
下面我們將兩個(gè) .a進(jìn)行一個(gè)合并 這時(shí)我們就又用到一個(gè)命令 libtool
同樣的看一下自描述
libtool-創(chuàng)建庫
ranlib-添加或更新存檔庫的目錄libtool命令獲取指定的輸入對象文件奴拦,并創(chuàng)建一個(gè)庫媒鼓,用于鏈接編輯器ld(1)。庫的名稱由output指定(指向-o標(biāo)志的參數(shù))。輸入對象文件可以是包含對象文件(“通用”文件绿鸣、歸檔文件疚沐、對象文件)的任何正確格式。Libtool不會(huì)將任何非對象輸入文件放入輸出庫(與ranlib不同潮模,ranlib允許在其操作的歸檔文件中進(jìn)行此操作)亮蛔。
當(dāng)從相同CPU類型和不同CPU子類型的對象生成“通用”文件時(shí),libtool和ranlib最多為每個(gè)CPU類型創(chuàng)建一個(gè)庫擎厢,而不是在通用文件中為每個(gè)CPU類型和CPU子類型的唯一配對創(chuàng)建一個(gè)單獨(dú)的庫究流。因此,每個(gè)庫的結(jié)果CPU子類型是該CPU類型的\u ALL CPU子類型动遭。此策略強(qiáng)烈鼓勵(lì)庫的實(shí)現(xiàn)者創(chuàng)建一個(gè)庫芬探,該庫選擇在運(yùn)行時(shí)而不是在鏈接時(shí)運(yùn)行的最佳代碼。Libtool可以創(chuàng)建動(dòng)態(tài)鏈接的共享庫(使用動(dòng)態(tài))厘惦,也可以創(chuàng)建靜態(tài)鏈接(存檔)庫(使用-static)偷仿。
好了知道大概描述了我們開始實(shí)操
? 靜態(tài)庫合并 libtool \
-static \
-o \
libABTest.a \
libA_LibTest.a \
libB_LibTest.a
查看合并的 libABTest.a 的.o合集
? 靜態(tài)庫合并 ar -t libABTest.a
__.SYMDEF SORTED
A_LibTest.o
A_Manager.o
A_Tool.o
B_Manager.o
B_LibTest.o
B_Tool.o
- 此時(shí)可以看到 兩個(gè)庫的.o文件 被合并在了一起。
- 此時(shí)庫.o已經(jīng)合并 那是不是就是一個(gè)處理頭文件的問題了宵蕉?
Framework
Mac OS/iOS平臺(tái)還可以使用Framework. Framework實(shí)際上是一種打包方式酝静,將庫的二進(jìn)制文件,頭文件和有關(guān)的資源打包在一起羡玛,方便管理和分發(fā)别智。
Framework 和系統(tǒng)的UIKit.Framework 還是有很大的區(qū)別。系統(tǒng)的Framework 不需要拷貝到目標(biāo)程序中稼稿,我們自己做出來的Framework 哪怕是動(dòng)態(tài)的亿遂,最后也還是要拷貝到App中(App 和 Extension的Bundle 是共享的),因此蘋果又把這種Framework稱為 Embedded Framework.
Embedded Framework:
開發(fā)中使用的動(dòng)態(tài)庫會(huì)被放入到ipa下的framework目錄下渺杉,基于沙盒運(yùn)行蛇数。
不同的App使用相同的動(dòng)態(tài)庫,并不會(huì)只在系統(tǒng)中存在一份是越。而是會(huì)在多 個(gè)App中各自打包耳舅、簽名、加載一份倚评。
- 從上面的描述想必大家也 明白了Framework其實(shí)就是 即將庫本身的格式還有頭文件 簽名 以資源文件 放在了一起浦徊。
手動(dòng)創(chuàng)建Framework
先看一下 AFNetworking的franmework的目錄
我們按照它的格式手動(dòng)來創(chuàng)建一個(gè)
1、我們以 A_libTest 靜態(tài)庫為例 在我們項(xiàng)目中 來鏈接它 并執(zhí)行天梧。
2盔性、新建文件夾并改為A_LibTest .framework 將上面編譯出來的libA_LibTest.a 放進(jìn) A_LibTest .framework 中 并新建立 heards 文件夾 將頭文件 扔進(jìn)去,按照Framework的格式我們將lib 還有.a 都去掉
- 通過??步驟 我們已經(jīng)創(chuàng)建好了一個(gè)Framework 下面我們進(jìn)行連接看一下
看一下準(zhǔn)備的test.m 它引入了 Framework中的一個(gè)頭文件A_Manager.h 并且main函數(shù)里使用了 此類的一個(gè)方法 -(void)A_Manager_TestFunction
下面我們來進(jìn)行編譯一下
clang -x objective-c \
-target x86_64-apple-macos10.13 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-I./Frameworks/A_LibTest.framework/Headers \
-c test.m -o test.o
- 可以看到被我們編譯為了.o
下面開始 鏈接 按照我們上面講的三要素 1 呢岗、指定目錄尋找頭文件(-大i)2冕香、指定庫文件路徑 -L 3蛹尝、指定鏈接庫文件名稱 那么那是 .a 或者是 .dylib 下面看一下Framework的手動(dòng)鏈接
clang -target x86_64-apple-macos10.13 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-F./Frameworks \
-framework A_libTest \
test.o -o test
- 首先 可以看到 前面命令是一樣的
- -F 指定Framework 所在的目錄 當(dāng)前我們的在./Frameworks
-
-framework 指定要鏈接哪個(gè)Framework 當(dāng)前我們鏈接的是 A_libTest
屏幕快照 2021-03-10 下午11.15.37.png - 可以看到我們已經(jīng)鏈接成功并成功的輸出了可執(zhí)行文件
下面我們運(yùn)行一下看能否調(diào)用成功
重要
通過上面的操作我們知道了:鏈接成功一個(gè)Framework:
1、-l<directory> (大i)指定目錄尋找頭文件
xcode: Header Search Paths
2悉尾、-F<directory> 指定目錄尋找framewok
xcode: :framework search path
3突那、-framework<framework_name> 指定鏈接的framework名稱
xcode: Other link flags 配置的 如-framework AFNetworking
通過shell 腳本來 操作
環(huán)境準(zhǔn)備
開始編寫腳本build.sh
首先 將 test.m 編譯成中間代碼
echo "-----開始編譯test.m"
clang -x objective-c \
-target x86_64-apple-macos10.13 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-I./StaticLibrary \
-c test.m -o test.o
然后進(jìn)入到StaticLibrary 中將 A_Manager 編譯成中間代碼
echo "-----開始進(jìn)入StaticLibrary目錄"
pushd ./StaticLibrary
echo "-----開始編譯A_Manager.m 為目標(biāo)文件"
clang -x objective-c \
-target x86_64-apple-macos10.13 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-I./Frameworks/A_LibTest.framework/Headers \
-c A_Manager.m -o A_Manager.o
將 A_Manager.o 打包為.a
echo "-----開始將目標(biāo)文件打包為 libA_Manager.a"
ar -rc libA_Manager.a A_Manager.o
退出當(dāng)前目錄 將test.o 鏈接 libA_Manager.a 并生成可執(zhí)行文件EXE
echo "-----開始退出StaticLibrary目錄"
popd
echo "-----將test.o 鏈接 libA_Manager.a 并生成可執(zhí)行文件EXE"
clang -target x86_64-apple-macos10.13 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk \
-L./StaticLibrary \
-lA_Manager \
test.o -o test
開始運(yùn)行./build.sh
此時(shí)我們可以看到 每一步的操作用腳本來實(shí)現(xiàn)是如此簡單
如遇到:zsh: permission denied: ./build.sh
解決: chomd +x ./build.sh
運(yùn)行test
成功被打印
dead_strip
通過如下命令我們查看一下 上面??我們編譯出來的 mach-O 看它的__Text section
objdump --macho -d test
- 可以看到包含靜態(tài)庫的符號 在連接的時(shí)候會(huì)把靜態(tài)庫的符號放在一起。保存在可執(zhí)行文件中构眯。
我們將main函數(shù)里使用到的方法去掉
- 這里不妨停幾秒想一下 我們引入了頭文件 但是并沒有使用它里面的任何代碼愕难,那么生成的可執(zhí)行文件test有沒有包含 A_Manager.m中的代碼?
運(yùn)行我們的腳本 ./build.sh 再次查看 mach-O
- 可以看到只有一個(gè)main 函數(shù) 這其實(shí)就是編譯器默認(rèn)的將沒有用到的符號給我們脫掉了惫霸。
這里我們想一個(gè)點(diǎn) 分類是在運(yùn)行時(shí)創(chuàng)建的還是 在編譯時(shí)創(chuàng)建的猫缭?肯定時(shí)運(yùn)行時(shí)了。
所以分類會(huì)出現(xiàn)問題壹店?
通過例子來展示
通過 workspace 來管理 兩個(gè) project 一個(gè)是主項(xiàng)目 一個(gè)是 我們的靜態(tài)庫Framework
-
主工程調(diào)用了靜態(tài)庫A 的一個(gè) A_Test方法
屏幕快照 2021-03-19 上午12.07.40.png -
靜態(tài)庫 A_Test方法調(diào)用了 分類 A+Test 的方法 testCategoryMethod方法
屏幕快照 2021-03-19 上午12.08.10.png - A+Test 的方法打印
此時(shí)不妨想一想 會(huì)調(diào)用成功嗎饵骨?
- 不成功又為啥?
這就是因?yàn)橛捎诜诸愂沁\(yùn)行時(shí)加載的茫打,所以 編譯器默認(rèn)將它符合進(jìn)行了死代碼剝離居触。
如何解決
-all_load : 全部加載不要給我脫
-ObjC : 只保留OC的其他的該脫脫
-force_load:指定 哪些靜態(tài)庫你需要脫
dead strip 兩種方案的區(qū)別
方案一
-noall_load /-all_load /-ObjC /-force_load<file> :在鏈接靜態(tài)庫的時(shí)候進(jìn)行死代碼刪除。
方案二
linking
dead Code Stripping : xxx 老赤,它是鏈接器給我們提供了一種優(yōu)化方式轮洋。
通過實(shí)操我們直觀的看一下他們的區(qū)別
沿用上面代碼
- 寫一個(gè)全局符號 并在main函數(shù)中調(diào)用
- 還寫了一個(gè)本地符號
-
通過上篇文章我們寫的腳本編譯一下
屏幕快照 2021-03-22 下午10.51.50.png - 此時(shí)我們想一下 我編譯出來的可執(zhí)行文件有沒有包含 libA_Manager.a這個(gè)靜態(tài)庫的代碼?
沒有抬旺?為什么弊予?因?yàn)槲覀儧]有使用靜態(tài)庫的代碼所以它默認(rèn)是-noall_load
我們來查看一下 我們編譯出來的 test mach-O
obdump --macho -d test
- 可以看到 并沒有 靜態(tài)庫的代碼
下面我們修改.sh 給連接器傳入 -all_load
- 再次編譯并運(yùn)行
再次查看我們的 test mach-O
- 此時(shí)發(fā)現(xiàn)我們沒有用到的靜態(tài)庫的函數(shù)也被存在了mach-o中,所以說方案一的參數(shù)設(shè)定對靜態(tài)庫來說是非常有用的开财。
下面我們看一下 ld的優(yōu)化方案 dead strip
- 翻譯過來就是 移除代碼或方法汉柒,沒有被入口點(diǎn)或?qū)С龇栍玫降摹>蜁?huì)被刪除
我們再回過頭來看一下我們的test.m
我們在.sh 中拼接上 -dead_strip 再次編譯
在此查看一下符號表
- 此時(shí)我們看到 明明 test.m我寫了一個(gè)全局符號责鳍,但是我并沒有用到碾褂,此時(shí)就會(huì)被干掉。
下面我們將其在main函數(shù)中使用历葛,再次編譯并查看符號表
- 此時(shí)發(fā)現(xiàn) 現(xiàn)在它出現(xiàn)在了符號表中
從這里我們就可以分析出 dead_strip和我們的n-noall_load all_load 等 它并不是一個(gè)東西
它只是我們的鏈接器給我提供的一種優(yōu)化方式正塌,它是有一定規(guī)則的
1、沒有被我們?nèi)肟邳c(diǎn)使用就被干掉恤溶,
2乓诽、沒有被導(dǎo)出符號用到就會(huì)干掉
下面我在將 all_load 添加上
編譯并 再次查看符號表
- 此時(shí)發(fā)現(xiàn) 除了OC的其他的全部被脫掉了 為啥?因?yàn)镺C是動(dòng)態(tài)語言現(xiàn)在給干掉它不敢啊咒程。
拓:還可以通過-Xlinker -why_live -Xlinker _xxxx 來查看這個(gè)符號為什么沒有被干掉
所以現(xiàn)在我們知道 dead strip 和 all_load /noall_load 是兩碼事鸠天。