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

寫好一個項目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項目

編譯成功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格式的內(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

絕對路徑無法通用,項目移動后就無法找到汪茧∫窝牵可執(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)系作者
  • 序言:七十年代末取董,一起剝皮案震驚了整個濱河市夷家,隨后出現(xiàn)的幾起案子脖隶,更是在濱河造成了極大的恐慌嗅定,老刑警劉巖顾患,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件琳拭,死亡現(xiàn)場離奇詭異,居然都是意外死亡描验,警方通過查閱死者的電腦和手機(jī)白嘁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膘流,“玉大人絮缅,你說我怎么就攤上這事鲁沥。” “怎么了耕魄?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵画恰,是天一觀的道長。 經(jīng)常有香客問我吸奴,道長允扇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任则奥,我火速辦了婚禮考润,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘读处。我一直安慰自己糊治,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布罚舱。 她就那樣靜靜地躺著井辜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪管闷。 梳的紋絲不亂的頭發(fā)上粥脚,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機(jī)與錄音包个,去河邊找鬼刷允。 笑死,一個胖子當(dāng)著我的面吹牛赃蛛,可吹牛的內(nèi)容都是我干的恃锉。 我是一名探鬼主播搀菩,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼呕臂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了肪跋?” 一聲冷哼從身側(cè)響起歧蒋,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎州既,沒想到半個月后谜洽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吴叶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年阐虚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚌卤。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡实束,死狀恐怖奥秆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情咸灿,我是刑警寧澤构订,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站避矢,受9級特大地震影響悼瘾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜审胸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一亥宿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧歹嘹,春花似錦箩绍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至怎抛,卻和暖如春卑吭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背马绝。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工豆赏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人富稻。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓掷邦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親椭赋。 傳聞我的和親對象是個殘疾皇子抚岗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容