場景一:鏈接動態(tài)庫AFN
一、準(zhǔn)備工作
準(zhǔn)備一個test.m
文件卷要,包含代碼如下:
#import <Foundation/Foundation.h>
#import <AFNetworking.h>
int main(){
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSLog(@"testApp----%@", manager);
return 0;
}
二膘侮、指令操作
編譯指令
clang -target x86_64-apple-macos10.15.7 -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 -target x86_64-apple-macos10.15.7 -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk -L./AFNetworking -lAFNetworking test.o -o test
注意:
- 與前文中靜態(tài)庫使用過的指令是相同的。
-
clang
可以自動識別語言
三钱烟、執(zhí)行結(jié)果
- dyld: Library not loaded:XXX
- Referenced from:XXX
- Reason: image not found
四嫡丙、原因拴袭??
自己生成一個動態(tài)庫研究看看鏈接動態(tài)庫原理曙博。
場景二:鏈接動態(tài)庫原理
一稻扬、準(zhǔn)備工作
編寫一個test.m
#import <Foundation/Foundation.h>
#import "TestExample.h"
int main(){
NSLog(@"testApp----");
TestExample *manager = [TestExample new];
[manager lg_test: nil];
return 0;
}
新建一個文件夾dylib
,里面包含兩個文件TestExample.h
和TestExample.m
羊瘩,TestExample.h
的代碼如下
#import <Foundation/Foundation.h>
@interface TestExample : NSObject
- (void)lg_test:(_Nullable id)e;
@end
TestExample.m
的代碼如下:
#import "TestExample.h"
@implementation TestExample
- (void)lg_test:(_Nullable id)e {
NSLog(@"TestExample----");
}
@end
二泰佳、編寫腳本
敲指令過于麻煩,寫一個腳本可以重復(fù)使用尘吗,用來控制編譯鏈接整個過程逝她。腳本如下:
echo "編譯test.m --- test.o"
clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-I./dylib \
-c test.m -o test.o
pushd ./dylib
echo "編譯TestExample.m --- TestExample.o"
clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-c TestExample.m -o TestExample.o
echo "編譯TestExample.m --- libTestExample.dylib"
# -dynamiclib: 動態(tài)庫
clang -dynamiclib -target x86_64-apple-macos10.15.7 -fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
TestExample.o -o libTestExample.dylib
popd
echo "鏈接libTestExample.dylib -- test EXEC"
clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-L./dylib \
-lTestExample \
test.o -o test
將.o
編譯成動態(tài)庫
clang -dynamiclib -target x86_64-apple-macos10.15.7 -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk TestExample.o -o libTestExample.dylib
三、結(jié)果
還是出錯睬捶,依然是那個image not found
錯誤黔宛。
四、思考
到底什么是動態(tài)庫擒贸?
與靜態(tài)庫相反臀晃,動態(tài)庫在編譯時并不會被拷?到?標(biāo)程序中,?標(biāo)程序中只會存儲指向動態(tài)庫的引?介劫。等到程序運(yùn)?時徽惋,動態(tài)庫才會被真正加載進(jìn)來。
在上文中座韵,.O
文件直接變成了一個靜態(tài)庫险绘,靜態(tài)庫是一個.o
文件的集合踢京。所以靜態(tài)庫可以看做.o
文件,是中間產(chǎn)物宦棺,我們可以用靜態(tài)庫去生成我們的可執(zhí)行文件瓣距,或者是動態(tài)庫。
五代咸、將靜態(tài)庫鏈接為動態(tài)庫
為了進(jìn)一步研究蹈丸,我們先將TestExample.o
生成靜態(tài)庫,再將靜態(tài)庫連接成動態(tài)庫呐芥。在腳本中將生成動態(tài)庫代碼替換如下:
echo "編譯TestExample.o --- libTestExample.a"
libtool -static -arch_only x86_64 TestExample.o -o libTestExample.a
echo "編譯TestExample.m --- libTestExample.dylib"
ld -dylib -arch x86_64 \
-macosx_version_min 11.1 \
-syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-lsystem -framework Foundation \
libTestExample.a -o libTestExample.dylib
執(zhí)行錯誤:未定義符號白华。在test.m
類中,使用了TestExample
類的lg_test
方法贩耐。未能找到動態(tài)庫中的導(dǎo)出符號。
原因:在動態(tài)庫在鏈接靜態(tài)庫時厦取,由于靜態(tài)庫默認(rèn)是-noall_load
潮太,符合代碼剝離條件,符號被剝離虾攻。
解決符號問題:在動態(tài)庫鏈接靜態(tài)庫時铡买,指定-all_load
參數(shù)。
修改后再重新執(zhí)行腳本:
注意:靜態(tài)庫是
.o
文件的合集霎箍,動態(tài)庫是.o
文件鏈接過后的> 產(chǎn)物奇钞。動態(tài)庫是鏈接編譯的最終產(chǎn)物,跟可執(zhí)行文件是一個等級漂坏。
場景三:通過tdb文件加深動態(tài)庫理解
場景二中生成動態(tài)庫命令:
clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-L./dylib \
-lTestExample \
test.o -o test
test.o在鏈接成為動態(tài)庫的時候景埃,到底用到了里面哪些東西?
一顶别、準(zhǔn)備工作
寫好一個項目LoginAPP
谷徙,什么都沒有的APP
。寫好一個庫叫SYCSSColor
驯绎,SYCSSColor
只有一個headers
和一個tdb
格式的文件完慧。
使用SYCSSColor
這個tdb
格式的動態(tài)庫,在LoginApp.debug.xcconfig
中設(shè)置HEADER_SEARCH_PATHS
剩失。
直接將SYCSSColor.tbd
放到LoginAPP
的Frameworks文件夾下面屈尼。
在ViewController.m
中使用SYCSSColor
庫。
#import <SYColor.h>
SYColor *color = [SYColor new];
不設(shè)置FRAMEWORK_SEARCH_PATHS
和OTHER_LDFLAGS
拴孤。沒有設(shè)置在哪個路徑下查找?guī)炱⑵纾矝]有設(shè)置庫名稱。
二演熟、command+B
編譯LoginAPP
項目
編譯成功U墙贰!!
到底什么是tdb
格式蚕冬?
tbd
:全稱是text-based stub libraries
免猾,本質(zhì)上就是?個YAML
描述的?本?件(類似于json
文件,也就是配置文件)
tbd
格式的作?:
- ?于記錄動態(tài)庫的?些信息囤热,包括導(dǎo)出的符號猎提、動態(tài)庫的架構(gòu)信息、動態(tài)庫的依賴信息
- ?于避免在真機(jī)開發(fā)過程中直接使?傳統(tǒng)的
dylib
- 對于真機(jī)來說旁蔼,由于系統(tǒng)動態(tài)庫(例如
foundation
庫等)都是在設(shè)備上锨苏,在Xcode
上使?基于tbd
格式的偽Framework
可以??減少Xcode
的??
原來在tdb
文件中包含了導(dǎo)出符號SYColor
。所以我們在編譯時棺聊,能找到導(dǎo)出符號伞租,編譯就不會報錯。
三限佩、總結(jié)
我們通過clang XXX -L./dylib -lTestExample
去鏈接一個庫的時候葵诈,只需要知道導(dǎo)出符號的位置就可以了,不需要源碼就可以編譯成功祟同。
但是運(yùn)行還是會報錯image not found
作喘,運(yùn)行時DYLD
會動態(tài)加載動態(tài)庫,找不到這個符號的真實(shí)地址晕城。
-L
和-l
本質(zhì)上是用來給動態(tài)庫說明導(dǎo)出符號在什么地方泞坦。靜態(tài)庫在鏈接的時候,符號表已經(jīng)合并在一起了砖顷,不需要這步操作贰锁。
場景四:構(gòu)建一個動態(tài)庫的Framework
不管動態(tài)庫還是靜態(tài)庫,庫里面結(jié)構(gòu)都是一樣的滤蝠。Header
保存頭文件李根;庫文件:靜態(tài)庫是.a
,動態(tài)庫是.dylib
几睛。
一房轿、準(zhǔn)備工作
test.m
中main
方法調(diào)用TestExample
類的方法,各打印一條語句所森。
二囱持、編寫腳本
1、在TestExample.framework
目錄下的腳本中編寫如下內(nèi)容:
echo "-------------編譯TestExample.m to TestExample.o------------------"
clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-I./Headers \
-c TestExample.m -o TestExample.o
echo "-------------TestExample.o to TestExample------------------"
clang -dynamiclib \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
TestExample.o -o TestExample
2焕济、在test.m
所在的目錄里面的腳本中編寫如下內(nèi)容:
echo "-------------編譯test.m to test.o------------------"
clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-I./Frameworks/TestExample.framework/Headers \
-c test.m -o test.o
echo "-------------將test.o鏈接成可執(zhí)行文件------------------"
clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-F./Frameworks \
-framework TestExample \
test.o -o test
三纷妆、執(zhí)行結(jié)果
還是報image not found
錯誤
原因:為什么產(chǎn)生image not found?
這個問題要從DYLD
加載一個動態(tài)庫說起晴弃。
- 當(dāng)
dyld
加載一個Mach-O
時掩幢,在Mach-O
中會有一個名稱為LC_LOAD_DYLIB
的Load Command
逊拍,它里面存儲了動態(tài)庫的路徑 - 動態(tài)庫是運(yùn)行時加載的,它的加載方式就是
dyld
通過路徑找到對應(yīng)的動態(tài)庫 - 如果路徑有誤际邻,導(dǎo)致運(yùn)行時
dyld
無法找到動態(tài)庫芯丧,就會提示image not found
錯誤
查看Mach-O
中LC_LOAD_DYLIB
信息
使用otool -l test | grep 'DYLIB' -A 5
命令,查看Mach-O
中動態(tài)庫的路徑.世曾。
我們發(fā)現(xiàn)有完整的路徑/System/Library/Frameworks/
和/usr/lib/libSystem
缨恒,這些庫都可以正常加載。也存在路徑有問題的動態(tài)庫轮听,也就是圖中標(biāo)注的骗露,這正是導(dǎo)致image not found的原因。
-
@rpath
未知導(dǎo)致路徑無效血巍,無法找到動態(tài)庫萧锉。 - 在場景四
test
的自定義的TestExample
動態(tài)庫,它的路徑只有一個TestExample
名稱述寡,相當(dāng)于和Mach-O
平級柿隙,在運(yùn)行時無法找到動態(tài)庫。
解決image not found
問題產(chǎn)生的原因是路徑不對辨赐,說明需要在動態(tài)庫中,將動態(tài)庫自身路徑補(bǔ)充完整京办。
在什么時候補(bǔ)充?在動態(tài)庫產(chǎn)生的時候掀序,也就是將.o
文件鏈接成動態(tài)庫時。
動態(tài)庫是鏈接時不會復(fù)制惭婿,程序運(yùn)行時由系統(tǒng)動態(tài)加載到內(nèi)存不恭。所以在可執(zhí)行文件鏈接動態(tài)庫時,動態(tài)庫并未在可執(zhí)行文件中财饥,只是一個包含地址的配置文件换吧。
解決辦法一:給現(xiàn)有動態(tài)庫添加路徑
使用install_name_tool
命令給動態(tài)庫添加路徑。改變動態(tài)庫的install name
钥星,相當(dāng)于所在路徑沾瓦。使用-id (name)
參數(shù),改變動態(tài)庫所在路徑谦炒。
在場景一中添加絕對路徑
install_name_tool -id /Users/chenshuangchao/Desktop/完成鏈接動態(tài)庫AFN/AFNetworking/libAFNetworking.dylib libAFNetworking.dylib
在場景二中添加相對路徑@rpath
第一步贯莺、給動態(tài)庫添加install_name
install_name_tool -id @rpath/dylib/libTestExample.dylib libTestExample.dylib
第二步、給可執(zhí)行文件添加@rpath
宁改,使用install_name_tool -add_rpath
缕探。
install_name_tool -add_rpath /Users/chenshuangchao/Desktop/完成動態(tài)庫原理 test
在場景四中@rpath
也使用相對路徑
@executable_path
:表示可執(zhí)?程序所在的?錄,解析為可執(zhí)??件的絕對路徑还蹲。@loader_path
:表示被加載的Mach-O
所在的?錄爹耗。每次加載時耙考,都可能被設(shè)置為不同的路徑,由上層指定潭兽。
在TestExample
的build.sh
中修改如下參數(shù)倦始,在生成動態(tài)庫時,傳遞一個install_name
-Xlinker -install_name -Xlinker @rpath/Frameworks/TestExample.framework/TestExample \
在生成test
可執(zhí)行文件的腳本中讼溺,添加如下參數(shù)楣号,在生成test
可執(zhí)行文件時,添加rpath
-Xlinker -rpath -Xlinker @loader_path \
解決辦法二:生成時給動態(tài)庫添加路徑
- 鏈接成為動態(tài)庫時怒坯,指定相對路徑炫狱。使用ld命令的
-install_name
參數(shù),在鏈接成為動態(tài)庫時剔猿,指定所在路徑视译。 - 在生成可執(zhí)行文件時,設(shè)置
@rpath
路徑归敬。誰鏈接動態(tài)庫酷含,誰提供@rpath
重點(diǎn):@rpath
絕對路徑無法通用,項目移動后就無法找到汪茧∫窝牵可執(zhí)行文件和動態(tài)庫之間約定了一個規(guī)則,可執(zhí)行文件提供路徑@rpath
舱污,動態(tài)庫提供相對于可執(zhí)行文件的路徑呀舔。
-
Runpath search Paths
:dyld搜索路徑,誰需要鏈接動態(tài)庫誰就需要提供@rpath
扩灯。 - 運(yùn)行時
@rpath
指示dyld按順序搜索路徑列表媚赖,以找到動態(tài)庫烈掠。 -
@rpath
保存一個或 多個路徑的變量堡距。
動態(tài)庫自己指定相對路徑
在clang
中指定-install_name
天梧,加上如下參數(shù):
-Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample
在ld
中添加-install_name
兢卵,無須添加-Xlinker
:
-install_name @rpath/TestExample.framework/