iOS静态库和动态库

什么是库,使用库有哪些好处?库就是将代码编译成一个二进制文件,再加头文件。常见的库文件格式有.a .dylib .tbd .framework .xcframework。使用库文件可以在不暴露源码的情况供别人使用,开发中将一些不常修改的代码打包成库也可以减少编译的时间。库分为静态库和动态库,在面试中经常会问二者的区别,了解它们的本质就需要亲自探索一番,本文带你一步步探索库的本质。有兴趣的同学建议动手试验一遍,然后再阅读一遍,搞不明白你找我!!

静态库的本质探索

静态库通常是以.a或.framework为后缀的库文件,先准备一个macOS静态库.a文件,我就以AFNetworking为例,通过两个命令filear来查看一下libAFNetworking.a,从终端打印描述可以看出它是一个文档格式,里面是.o文件。

image.png

接下来模拟App链接静态库的过程,使用一个.o文件来链接libAFNetworking.a

  • 创建一个test.m文件,然后在test.m文件中引用AFNetworking,test.m代码:
#import <Foundation/Foundation.h>
#import <AFNetworking.h>

int main(){
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    NSLog(@"testApp----%@", manager);
    return 0;
}
  • 使用clang将test.m文件生成test.o文件,终端命令
    clang -x objective-c -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -I./AFNetworking -c test.m -o test.o

    • -isysroot是因为使用了Foundation库,需要指定库的位置,使用find /Applications/Xcode.app/Contents -name SDKs可以快速进行定位到SDKs文件夹,进入文件夹再使用ls命令列出sdk名称
    • -I是指定AFNetworking头文件位置,对应Xcode中的Header search path,在目标文件中有一个重定位符号表,它保存了当前文件里面使用的所有符号,在链接的时候链接器会根据重定位符号表再去重新定位查找具体的符号信息,因此在生成目标文件时我们只需要有一个头文件,能够生成重定位符号即可。
    • 关于这些命令的介绍都可以通过man clang来查看解释,其他的就不再一一介绍
  • 生成可执行文件,终端命令clang -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -L./AFNetworking -lAFNetworking test.o -o test

    • -L后面的是静态库的路径,相当于Xcode配置项里面的Libarary Search Path,
    • -l后面是静态库的名称,这里有一个查询规则是会按lib加上-l后面的组合在一起进行查找,因此libAFNetworking.a在这里只需要-lAFNetworking即可。
  • 终端lldb运行测试,在终端输入命令lldb -file test 或者先lldbfile test,进入lldb后使用r运行。可以看到在终端打印了我们test.m中输出的语名,并且成功打印出了引用的AFNetworking库中类创建的对象

  • 将test文件放到任意路径尝试,均可以运行打印出对象,说明最终链接会将所有的.o文件、静态库文件进行合并。

➜  Test lldb
(lldb) file test
Current executable set to '/Users/Peny/Desktop/Test/test' (x86_64).
(lldb) r
Process 78280 launched: '/Users/Peny/Desktop/Test/test' (x86_64)
2021-01-23 02:19:16.032613+0800 test[78280:5833698] testApp----<AFHTTPSessionManager: 0x10040f1c0, baseURL: (null), session: <__NSURLSessionLocal: 0x100410c70>, operationQueue: <NSOperationQueue: 0x10040fef0>{name = 'NSOperationQueue 0x10040fef0'}>
Process 78280 exited with status = 0 (0x00000000)
(lldb)
  • 侧面印证:将一个.o文件当成静态库来被链接,如果能成功说明.o等效于一个静态库,具体方式是创建一个TestB.h和TestB.m并定义一个OC方法打印日志,在test.m中调用oc方法。将TestB.m通过clang转成.o然后使用ar命令将.o文件转成.a静态库格式,完整命令ar -rc libTestB.a TestB.o,用test.o来链接libTestB.a,最后通过lldb--> file test --> r,看是否能够成功调用TestB中的方法(本人亲测可以打印,注意要使用MacOS.sdk,不然无法在终端调试哦~)

通过以上测试可以总结出:

  • 静态库的本质就是.o文件的合集
  • 要成功链接一个静态库有三要素:库名称、头文件和库文件路径。
  • 静态库链接之后所有的符号都将合并在一起,源文件就没用了,好处就是能直接运行,但是也会使可执行文件变大,占用__text section空间,苹果对它目前限制500M

动态库探索

常见的动态库格式有.dylib.tbd.framework。我们采用与静态库相同的方式去链接一个AFNetworking的动态库libAFNetworking.dylib探索尝试,还是两个命令,编写脚本link_dylib.sh:

echo "编译test.m生成test.o==="
clang -x objective-c -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -I./AFNetworking -c test.m -o test.o
echo "链接libAFNetworking.dylib==="
clang -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -L./AFNetworking -lAFNetworking test.o -o test
echo "生成test可执行文件"

将脚本放到与AFNetworkingtest.m同级,添加可执行权限chmod +x link_dylib.sh,执行脚本./link_dylib.sh,报错了???

image.png

为了排除动态库的问题,我们再使用clang命令制作一个动态库再进行尝试,使用之前准备的TestB.hTestB.m文件作成一个动态库,将这两个文件放到dylib文件夹中,在dylib同级目录下编写build_dylib.sh脚本

echo "将test.m编译成test.o"
clang -x objective-c -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -I./dylib -c test.m -o test.o
pushd ./dylib
echo "将TestB.m编译成TestB.o"
clang -x objective-c -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -c TestB.m -o TestB.o
echo "clang -dynamiclib编译成动态库"
clang -dynamiclib -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk TestB.o -o libTestB.dylib
popd
echo "链接libTestB.dylib生成EXEC"
clang -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -L./dylib -lTestB test.o -o test

执行脚本可以看到test的exec文件已经生成,再次执行lldb -file test + r,还是报错!!难道是动态库生成的姿势不对?嗯。。。再换一种方式,既然静态库它是一个.o文件的合集,那是不是也可以将静态库链接成为一个动态库呢?继续折腾~

  • 使用clang命令将TestB.m编译成TestB.o文件
  • TestB.o打包成静态库,这次我们使用xcode官方自带的命令libtool -static
  • 直接使用链接器ld -dylib来链接
  • 生成可执行文件

步骤比较多,编写脚本build_a_dylib.sh:

MACOS_SDK=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk

echo "将test.m编译成test.o"
clang -x objective-c -fobjc-arc -isysroot $MACOS_SDK -I./dylib -c test.m -o test.o
pushd ./dylib
echo "将TestB.m编译成TestB.o"
clang -x objective-c -fobjc-arc -isysroot $MACOS_SDK -c TestB.m -o TestB.o

echo "libtool -static 将.o打包成静态库 libTestB.a"
libtool -static -arch_only x86_64 TestB.o -o libTestB.a

echo "ld -dylib 生成动态库 libTestB.dylib"
ld -dylib -arch x86_64 \
-macosx_version_min 10.15 \
-syslibroot $MACOS_SDK \
-lsystem \
-framework Foundation \
libTestB.a -o libTestB.dylib

popd

echo "链接libTestB.dylib生成EXEC"

clang -fobjc-arc -isysroot $MACOS_SDK -L./dylib -lTestB test.o -o test

添加执行权限chmod +x build_a_dylib.sh,运行,再次报错!!!

image.png

找不到符号TestB,但是动态库已经生成了,说明动态库dylib中没有这个导出符号,可以通过查看导出符号的命令进行验证objdump --macho --exports-trie ./dylib/libTestB.dylib,那么肯定是我们链接时出现了什么问题?使用man ld查看链接器,可以看到其中有几个命令:

-all_load   Loads all members of static archive libraries.
-ObjC       Loads all members of static archive libraries that implement an Objective-C class or category.
-force_load path_to_archive
                 Loads all members of the specified static archive library.  Note: -all_load forces all members of all archives to be loaded.  This option allows you to target a specific archive.
-noall_load This is the default.  This option is obsolete.

-noall_load为链接器的一个默认值,猜测当我们从静态库生成动态库的时候,它的符号因为没有被外部使用而被脱掉了才出现上面的错误信息,修改ld命令添加上-all_load或者-ObjC来再试一下,果然完美运行,再次使用objdump查看dylib的导出符号,赫然在列!再来lldb运行一下吧 。(用脚指头想也知道还是会报错。。。我就试一下~)

image.png

果不其然,依然是Libarary not loaded and image not found,实在是烦!!!不过也并非一无所获,至少可以看出动态库与静态库的区别,静态库只是一个.o文件的集合,而动态库它比静态库多了一个链接的步骤,它是一个最终链接产物,也就是说动态库它不能再进行合并

探索到了这里,显然还没有结束,明明已经编译链接成功,为什么在运行时会出现这个错误?接下来我们借助一个工具MachOView来查看可执行文件test,能报这个库找不到说明Load Commands中肯定是有这个LC_LOAD_DYLIB,它是不是有什么问题呢?与我们使用cocoapods引入的动态库对比一下看看

image.png

image.png

我好像发现了点什么。。。正常运行的项目里面引入的动态库指向的路径为@rpath/AFNetworking.framework/AFNetworking@rpath是什么暂且不管,后面的路径看起来就是动态库的所有路径啊,第六感告诉我运行报错很可能是因为libTestB.dylib的路径有问题,查看我们的test文件目录可以发现我们的test文件与libTestB.dylib并不在一个层级
image.png

大胆推测一下,如果将libTestB.dylib文件与test文件放在同一层级是否就能成功运行,怀着激动的心情将libTestB.dylib文件放到同一目录下,然后lldb -file test,在lldb下运行
image.png

天呐,运行成功了!!!我就说嘛,哈哈,我的第六感是很准的~,也就是说我们的动态库在链接成功之后,其符号仍然保存在库中,在运行时可执行文件会按照LC_LOAD_DYLIB中的路径去查找动态库中的符号。所以在我们开发中如果再遇到某某动态库Libarary not loaded时,十有八九就是路径的问题了。

终于搞清楚了之后还要解决引用路径的问题,这里不再码字说明,仅给出研究方向,研究@rpath@executable_path@load_path这三者究竟是如何使用的,对于这三者理解之后可以帮助我们解决一些动态库的链接依赖问题,这里给出一些简单的介绍,有兴趣的同学可以自行探索。

  • @rpath比较灵活,它是一个变量,一个占位,谁使用谁提供它的值,链接器可以通过install_name_tool-add_rpath设置值,可以有多个值
  • @executable_path代表可以执行程序所在的路径。
  • @loader_path表示加载它的Mach-O所在的目录,由它的上层来决定,用它可以解决动态库依赖动态库的复杂场景。

总结动态库的特点:

  • 动态库与静态库相比多了一步链接,它是链接的最终产物,动态库不能再次进行合并
  • 动态库在编译链接之后并没有将所有符号合并,在运行时会根据LC_LOAD_DYLIB中的路径去动态加载

再次建议:要想搞清楚原理一定要亲自动手尝试,不要轻信他人给出的结论,欢迎来怼!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容