iOS开发进阶五:动态库

场景一:链接动态库AFN

一、准备工作

准备的AFN动态库.png

准备一个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. 与前文中静态库使用过的指令是相同的。
  2. clang可以自动识别语言

三、执行结果

编译、链接和执行.png
  • dyld: Library not loaded:XXX
  • Referenced from:XXX
  • Reason: image not found

四、原因??

自己生成一个动态库研究看看链接动态库原理。

场景二:链接动态库原理

一、准备工作

编写一个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.mTestExample.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

二、编写脚本

敲指令过于麻烦,写一个脚本可以重复使用,用来控制编译链接整个过程。脚本如下:

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: 动态库
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编译成动态库

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

三、结果

自己生成的动态库链接依然报错.png

还是出错,依然是那个image not found错误。

四、思考

到底什么是动态库?

与静态库相反,动态库在编译时并不会被拷⻉到⽬标程序中,⽬标程序中只会存储指向动态库的引⽤。等到程序运⾏时,动态库才会被真正加载进来。

在上文中,.O文件直接变成了一个静态库,静态库是一个.o文件的集合。所以静态库可以看做.o文件,是中间产物,我们可以用静态库去生成我们的可执行文件,或者是动态库。

五、将静态库链接为动态库

为了进一步研究,我们先将TestExample.o生成静态库,再将静态库连接成动态库。在脚本中将生成动态库代码替换如下:

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
将静态库链接成动态库未定义符号错误.png

执行错误:未定义符号。在test.m类中,使用了TestExample类的lg_test方法。未能找到动态库中的导出符号。

原因:在动态库在链接静态库时,由于静态库默认是-noall_load,符合代码剥离条件,符号被剥离。

添加all-load.png

解决符号问题:在动态库链接静态库时,指定-all_load参数。

修改后再重新执行脚本:


将静态库链接成动态库还是出错.png

注意:静态库是.o文件的合集,动态库是.o文件链接过后的> 产物。动态库是链接编译的最终产物,跟可执行文件是一个等级。

场景三:通过tdb文件加深动态库理解

场景二中生成动态库命令:

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在链接成为动态库的时候,到底用到了里面哪些东西?

一、准备工作

写好一个项目LoginAPP,什么都没有的APP。写好一个库叫SYCSSColorSYCSSColor只有一个headers和一个tdb格式的文件。

TDB准备工作一.png

使用SYCSSColor这个tdb格式的动态库,在LoginApp.debug.xcconfig中设置HEADER_SEARCH_PATHS

直接将SYCSSColor.tbd放到LoginAPP的Frameworks文件夹下面。

ViewController.m中使用SYCSSColor库。

#import <SYColor.h>
SYColor *color = [SYColor new];

不设置FRAMEWORK_SEARCH_PATHSOTHER_LDFLAGS。没有设置在哪个路径下查找库,也没有设置库名称。

二、command+B编译LoginAPP项目

编译成功!!!

到底什么是tdb格式?

tbd:全称是text-based stub libraries,本质上就是⼀个YAML描述的⽂本⽂件(类似于json文件,也就是配置文件)

tbd格式的作⽤:

  • ⽤于记录动态库的⼀些信息,包括导出的符号、动态库的架构信息、动态库的依赖信息
  • ⽤于避免在真机开发过程中直接使⽤传统的dylib
  • 对于真机来说,由于系统动态库(例如foundation库等)都是在设备上,在Xcode上使⽤基于tbd格式的伪Framework可以⼤⼤减少Xcode的⼤⼩
    tdb格式的内容:包含导出符号.png

原来在tdb文件中包含了导出符号SYColor。所以我们在编译时,能找到导出符号,编译就不会报错。

三、总结

我们通过clang XXX -L./dylib -lTestExample 去链接一个库的时候,只需要知道导出符号的位置就可以了,不需要源码就可以编译成功。

但是运行还是会报错image not found,运行时DYLD会动态加载动态库,找不到这个符号的真实地址。

-L-l本质上是用来给动态库说明导出符号在什么地方。静态库在链接的时候,符号表已经合并在一起了,不需要这步操作。

场景四:构建一个动态库的Framework

不管动态库还是静态库,库里面结构都是一样的。Header保存头文件;库文件:静态库是.a,动态库是.dylib

一、准备工作

创建动态库Framework准备工作.png

test.mmain方法调用TestExample类的方法,各打印一条语句。

二、编写脚本

1、在TestExample.framework目录下的脚本中编写如下内容:

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所在的目录里面的脚本中编写如下内容:

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链接成可执行文件------------------"
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

三、执行结果

创建Framework动态库.png

还是报image not found错误

原因:为什么产生image not found?

这个问题要从DYLD加载一个动态库说起。

dyld加载动态库的流程.png

  • dyld加载一个Mach-O时,在Mach-O中会有一个名称为LC_LOAD_DYLIBLoad Command,它里面存储了动态库的路径
  • 动态库是运行时加载的,它的加载方式就是dyld通过路径找到对应的动态库
  • 如果路径有误,导致运行时dyld无法找到动态库,就会提示image not found错误

查看Mach-OLC_LOAD_DYLIB信息

使用otool -l test | grep 'DYLIB' -A 5命令,查看Mach-O中动态库的路径.。

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

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

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

我们发现有完整的路径/System/Library/Frameworks//usr/lib/libSystem,这些库都可以正常加载。也存在路径有问题的动态库,也就是图中标注的,这正是导致image not found的原因。

  • @rpath未知导致路径无效,无法找到动态库。
  • 在场景四test的自定义的TestExample动态库,它的路径只有一个TestExample名称,相当于和Mach-O平级,在运行时无法找到动态库。

解决image not found

问题产生的原因是路径不对,说明需要在动态库中,将动态库自身路径补充完整。
在什么时候补充?在动态库产生的时候,也就是将.o文件链接成动态库时。

动态库是链接时不会复制,程序运行时由系统动态加载到内存。所以在可执行文件链接动态库时,动态库并未在可执行文件中,只是一个包含地址的配置文件。

解决办法一:给现有动态库添加路径

使用install_name_tool命令给动态库添加路径。改变动态库的install name,相当于所在路径。使用-id (name)参数,改变动态库所在路径。

在场景一中添加绝对路径

install_name_tool -id /Users/chenshuangchao/Desktop/完成链接动态库AFN/AFNetworking/libAFNetworking.dylib libAFNetworking.dylib
场景一:添加路径后成功.png

在场景二中添加相对路径@rpath

第一步、给动态库添加install_name

install_name_tool -id @rpath/dylib/libTestExample.dylib libTestExample.dylib

第二步、给可执行文件添加@rpath,使用install_name_tool -add_rpath

install_name_tool -add_rpath /Users/chenshuangchao/Desktop/完成动态库原理 test
场景二:通过rpath方式指定动态库路径.png

在场景四中@rpath也使用相对路径

  • @executable_path:表示可执⾏程序所在的⽬录,解析为可执⾏⽂件的绝对路径。
  • @loader_path:表示被加载的Mach-O所在的⽬录。每次加载时,都可能被设置为不同的路径,由上层指定。

TestExamplebuild.sh中修改如下参数,在生成动态库时,传递一个install_name

-Xlinker -install_name -Xlinker @rpath/Frameworks/TestExample.framework/TestExample \

在生成test可执行文件的脚本中,添加如下参数,在生成test可执行文件时,添加rpath

-Xlinker -rpath -Xlinker  @loader_path \
场景四:install_name和rpath均使用相对路径.png

解决办法二:生成时给动态库添加路径

  • 链接成为动态库时,指定相对路径。使用ld命令的-install_name参数,在链接成为动态库时,指定所在路径。
  • 在生成可执行文件时,设置@rpath路径。谁链接动态库,谁提供@rpath
场景二脚本中修改脚本.png

场景二生成时指定install_name和rpath成功.png
场景四修改成功.png

重点:@rpath

绝对路径无法通用,项目移动后就无法找到。可执行文件和动态库之间约定了一个规则,可执行文件提供路径@rpath,动态库提供相对于可执行文件的路径。

  • Runpath search Paths:dyld搜索路径,谁需要链接动态库谁就需要提供@rpath
  • 运行时@rpath指示dyld按顺序搜索路径列表,以找到动态库。
  • @rpath保存一个或 多个路径的变量。

动态库自己指定相对路径

clang中指定-install_name,加上如下参数:

-Xlinker -install_name -Xlinker @rpath/TestExample.framework/TestExample 

ld中添加-install_name,无须添加-Xlinker

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

推荐阅读更多精彩内容