iOS開發(fā)進(jìn)階五:動態(tài)庫

場景一:鏈接動態(tài)庫AFN

一、準(zhǔn)備工作

準(zhǔn)備的AFN動態(tài)庫.png

準(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

注意:

  1. 與前文中靜態(tài)庫使用過的指令是相同的。
  2. clang可以自動識別語言

三、執(zhí)行結(jié)果

編譯、鏈接和執(zhí)行.png
  • 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.hTestExample.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é)果

自己生成的動態(tài)庫鏈接依然報錯.png

還是出錯,依然是那個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
將靜態(tài)庫鏈接成動態(tài)庫未定義符號錯誤.png

執(zhí)行錯誤:未定義符號。在test.m類中,使用了TestExample類的lg_test方法。未能找到動態(tài)庫中的導(dǎo)出符號。

原因:在動態(tài)庫在鏈接靜態(tài)庫時,由于靜態(tài)庫默認(rèn)是-noall_load,符合代碼剝離條件,符號被剝離。

添加all-load.png

解決符號問題:在動態(tài)庫鏈接靜態(tài)庫時,指定-all_load參數(shù)。

修改后再重新執(zhí)行腳本:


將靜態(tài)庫鏈接成動態(tài)庫還是出錯.png

注意:靜態(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格式的文件。

TDB準(zhǔn)備工作一.png

使用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_PATHSOTHER_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)備工作

創(chuàng)建動態(tài)庫Framework準(zhǔn)備工作.png

test.mmain方法調(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é)果

創(chuàng)建Framework動態(tài)庫.png

還是報image not found錯誤

原因:為什么產(chǎn)生image not found?

這個問題要從DYLD加載一個動態(tài)庫說起。

dyld加載動態(tài)庫的流程.png

  • 當(dāng)dyld加載一個Mach-O時,在Mach-O中會有一個名稱為LC_LOAD_DYLIBLoad Command,它里面存儲了動態(tài)庫的路徑
  • 動態(tài)庫是運(yùn)行時加載的,它的加載方式就是dyld通過路徑找到對應(yīng)的動態(tài)庫
  • 如果路徑有誤,導(dǎo)致運(yùn)行時dyld無法找到動態(tài)庫,就會提示image not found錯誤

查看Mach-OLC_LOAD_DYLIB信息

使用otool -l test | grep 'DYLIB' -A 5命令,查看Mach-O中動態(tài)庫的路徑.。

場景一:Mach-O中的load-command找不到rpath.png
場景二:Mach-O中的load-command.png

場景三:Mach-O中的load-command.png

場景四:Mach-O中的load-command.png

我們發(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
場景一:添加路徑后成功.png

在場景二中添加相對路徑@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方式指定動態(tài)庫路徑.png

在場景四中@rpath也使用相對路徑

  • @executable_path:表示可執(zhí)?程序所在的?錄,解析為可執(zhí)??件的絕對路徑。
  • @loader_path:表示被加載的Mach-O所在的?錄。每次加載時,都可能被設(shè)置為不同的路徑,由上層指定。

TestExamplebuild.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 \
場景四:install_name和rpath均使用相對路徑.png

解決辦法二:生成時給動態(tài)庫添加路徑

  • 鏈接成為動態(tài)庫時,指定相對路徑。使用ld命令的-install_name參數(shù),在鏈接成為動態(tài)庫時,指定所在路徑。
  • 在生成可執(zhí)行文件時,設(shè)置@rpath路徑。誰鏈接動態(tài)庫,誰提供@rpath
場景二腳本中修改腳本.png

場景二生成時指定install_name和rpath成功.png
場景四修改成功.png

重點(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/
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容