場景一:鏈接動態(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)備工作
寫好一個項(xiàng)目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項(xiàng)目
編譯成功?。。?/strong>
到底什么是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格式的內(nèi)容:包含導(dǎo)出符號.png
原來在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
絕對路徑無法通用,項(xiàng)目移動后就無法找到??蓤?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/
