最近整理一些項目以前依賴留下的問題,在使用 CocoaPods 和 Carthage 的時候引出了關(guān)于靜態(tài)庫和動態(tài)庫的思考,手動編譯靜態(tài)庫的朋友應(yīng)該知道,如果自己編譯了一個靜態(tài)庫,這個靜態(tài)庫依賴了 iOS 自帶的庫,即使你在 Xcode開發(fā)工具中顯式指定了依賴,當你在另一個工程中使用這個靜態(tài)庫的時候,依舊需要顯式在那個工程指定這個靜態(tài)庫所依賴的系統(tǒng)庫。
舉個栗子
AFNetworking 是個好東西,大家都喜歡用,在日常使用的時候,一般都是使用 CocoaPods 引入這個庫,筆者前面有篇文章分析了 CocoaPods 做了什么工作,但是卻沒有分析 CocoaPods 為何如此設(shè)計,這里先賣個關(guān)子不講。AFNetworking 依賴Security.framework MobileCoreServices.frameworkSystemConfiguration.framework 三個系統(tǒng)框架,如果我們將其編譯為靜態(tài)庫,然后這個靜態(tài)庫被其他工程依賴,那么其他工程依舊需要引入這三個框架。如果我們將其編譯為動態(tài)庫,則不需要依賴這三個框架。這個情況引起了筆者的興趣
分析
既然問題是編譯鏈接,那么就需要從動態(tài)庫和靜態(tài)庫入手分析,首先,我們需要明確的是,系統(tǒng)提供的 framework 都是動態(tài)庫的形式提供的。這很好理解,因為 UIKit 這類 framework 被使用的太頻繁了,內(nèi)存中完全只需要保留一份副本。這樣也能減輕 App 的大小。那么直接來著手模擬一下其編譯過程。
模擬靜態(tài)庫的編譯
創(chuàng)建一個動態(tài)庫用于模擬系統(tǒng)庫
dynamic.h
----------void hello();
dynamic.c
----------#include "dynamic.h"#include <stdio.h>
void hello() {
printf("Hello World");
}
然后編譯打包動態(tài)庫
> gcc -c -o dynamic.o dynamic.c # 編譯為對象文件
> gcc -shared dynamic.o -o libdynamic.so
創(chuàng)建一個靜態(tài)庫用于模擬第三方靜態(tài)庫
static.h
--------void sayHello();
static.c
--------#include "static.h"#include "dynamic.h"
void sayHello() {
hello();
}
然后編譯靜態(tài)庫
> gcc -c -o static.o static.c
> ar -r libstatic.a static.o
這里大家有沒有發(fā)現(xiàn)一個問題,靜態(tài)庫實際上并沒有鏈接動態(tài)庫,僅僅只是 include 了一個頭文件用于編譯通過。最終生成的靜態(tài)庫根本不知道動態(tài)庫的存在。然后創(chuàng)建一個帶有main 函數(shù)的程序
hello.c
-------#include "static.h"int main(int argc, char *argv[]) {
sayHello();
}
然后編譯
> gcc -o hello hello.c -L. -lstatic# 直接報錯沒有找到 hello() 的二進制代碼
Undefined symbols for architecture x86_64:
"_hello", referenced from:
_sayHello in libstatic.a(static.o)
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
模擬動態(tài)庫的編譯
創(chuàng)建一個動態(tài)庫用于模擬系統(tǒng)庫
dynamic1.h
----------void hello();
dynamic1.c
----------#include "dynamic1.h"#include <stdio.h>
void hello() {
printf("Hello World");
}
然后編譯打包動態(tài)庫
> gcc -c -o dynamic1.o dynamic1.c # 編譯為對象文件
> gcc -shared dynamic1.o -o libdynamic1.so
創(chuàng)建動態(tài)庫用于模擬第三方動態(tài)庫
dynamic2.h
--------void sayHello();
dynamic2.c
--------#include "dynamic2.h"#include "dynamic1.h"
void sayHello() {
hello();
}
然后編譯打包動態(tài)庫
> gcc -c -o dynamic2.o dynamic2.c # 編譯為對象文件
> gcc -shared dynamic2.o -o libdynamic2.so# 報錯,顯示 dynamic2.o 中沒有 hello() 二進制代碼
Undefined symbols for architecture x86_64:
"_hello", referenced from:
_sayHello in dynamic2.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
> gcc -shared dynamic2.o -o libdynamic2.so -L. -ldynamic1# 成功,無報錯
然后創(chuàng)建一個帶有 main 函數(shù)的程序
hello.c
-------#include "static.h"int main(int argc, char *argv[]) {
sayHello();
}
然后編譯
> gcc -o hello hello.c -L. -ldynamic2
總結(jié)
從上面兩個編譯過程大家應(yīng)該也能明白了,靜態(tài)庫實際上只是對象文件的打包,也就是說,只經(jīng)過了編譯過程,而沒有鏈接過程,編譯一個靜態(tài)庫甚至只需要滿足所有函數(shù)的聲明就行,而動態(tài)庫雖然沒有經(jīng)過正規(guī)的鏈接,但是實際上還是通過 gcc 做了跟其他動態(tài)庫的鏈接,動態(tài)庫自身有了依賴的概念。所以不需要在工程中顯式依賴了。這里可能有朋友想要問,這種知識有什么用處?實際上這種知識在依賴管理中有用,前面說過 CocoaPods 對工程修改了很多內(nèi)容,這是有原因的,因為 iOS8 之前是不存在動態(tài)庫的,只存在靜態(tài)庫,CocoaPods 不得不對目標工程也作出修改來添加對其他庫的依賴,因為靜態(tài)庫不知道自身依賴什么庫。而 Carthage 則只支持 iOS8 和動態(tài)庫,所以完全可以沒有侵入性,只需要提供一個動態(tài)庫,然后工程依賴這個動態(tài)庫就行了。
原文來自:推酷