一、库简介
1.1 库的形式
iOS这边库可以分为静态库(Static Library)和动态库(Dynamic Library)
- 动态库 Framework
- 静态库 Framework 和 .a 文件
1.2 库的区别
下文默认读者具有静态链接&动态链接相关知识,如有不明,请翻阅 mach-o分析 参考
相同点 :
两种库在构建的过程中,都需要经过编译,会将对应的 .m 文件编译成 .o 文件。这样也就造成了一个问题,任何编译配置宏等已经被展开后编译完成。也就是说,生成库时使用的 Debug 环境生成。即使将来被集成进项目中时,项目内配置 Release 环境出包,不会影响到库的逻辑。因为库中的代码是在生成是已经编译完成的,不随项目代码一起编译。
不同点 :
-
动态库在构建的过程中,需要经过静态链接。这里你没有看错,动态库的生成需要静态链接。而静态库的生成,不需要经过静态链接,仅仅只是简单的将对应的 .o 文件压缩。所以这里也可以通过命令行工具将 .o 文件重新解压缩出来。
这里我们重点说一下动态库,动态库和我们项目产出的主工程可执行文件对比,其编译、链接等过程是完全一样的。换句话说,动态库是一个没有 main 函数的可执行文件。
-
在使用上,动态库是在程序启动运行时,被动态链接后执行调用的。而静态库则参与程序的静态链接,被链入主工程的二进制可执行文件中。这也就是为什么,动态库需要被拷贝内嵌 (embed) 到包内,静态库不需要的原因。
同样,动态库 A 在被程序动态链接后,动态库 A 如果依赖动态库 B,也会动态链接 B。所以动态库 B 也需要被内嵌到包内,否则会报错“Library not loaded xxx”
1.3 总结
动态库和静态库,在生成时,因为其是否经过静态链接,产生了差异。动态库经过静态链接后,会经过符号决议、重定位等流程,会将依赖的静态库链接进来,也就是说,动态库会吸附静态库。如果依赖的是动态库,则走动态链接的流程。
在使用时,因为动态库只需要动态链接,所以不会在主工程编译阶段报错,但可能在运行阶段报找不到库。静态库则需要被主工程静态链接,所以当缺少符号或者符号重复冲突时,会在编译阶段报错。
这里注意区分库的生成时机、库被主工程引入编译的时机、主工程启动运行后的动态链接时机。
下面列出了库依赖及冲突的各种场景,尝试不看原因,看看是否能独立分析出,出现这种现象的原因。
二、库依赖场景下的符号冲突问题
2.1 两个静态库
2.1.1 两个静态库有相同符号:
- 场景:静态库A、B均采用Framework的方式来创建,其中 A、B 包含同一个类
Obj
,然后将 A、B 同时集成到工程中去。 - 结果:在链接(link)阶段报符号重复。
- 原因:A、B均需要参与主工程的静态链接,会在静态链接的符号决议过程中,发送冲突。
2.1.2 静态库 A 依赖静态库 B:
-
场景:静态库A、B均采用 Framework(.a类似) 的方式来创建,其中 A 依赖 B。A 库在 Framework Search Path 中正确设置 B 库路径(思考为什么可以不设置 Link Binary With Libraries)。A、B库代码如下:
// 静态库A @interface ObjA : NSObject + (void)test; @end @implementation ObjA + (void)test { NSLog(@"ObjA Test"); [ObjB test]; //依赖 B 库中的类方法 } @end // 静态库B @interface ObjB : NSObject + (void)test; @end @implementation ObjB + (void)test { NSLog(@"ObjB Test"); } @end // 主工程中调用 [ObjA test];
结果:主工程中,如果我们 A 正常使用,B 仅设置Framework Search Path,让工程可以正确搜索到Framework,但是没有设置linker flag,或者没有设置 Link Binary With Libraries。则会在编译的时候报缺少符号。
原因:A静态库生成过程,因为并没有经过静态链接,所以并不会包含 B 库的符号。A、B均需要参与主工程的静态链接,但此时B没有设置Link Binary With Libraries,所以会在静态链接的符号决议过程中,找不到对应的符号,报错。
推广:这里如果是静态库 .a 依赖静态库 .framework、.a 依赖 .a也是一样的情况。
注意:上述情况有一个例外:.framework 静态库 依赖 .a 静态库。在这种情况下,如果我们在 A 库中设置了Library Search Path 或 Link Binary With Libraries。会导致静态库的重新压缩,生成出来的 A 库会包含 B 库的.o文件。使用 A 库的时候,也就不再需要引入 .a 静态库B,否则会报符号冲突。如果不想 .a 静态库B被压缩进 .framework 静态库A,则.framework 静态库A仅仅将 .a库B的头文件引入即可,不需要设置Library Search Path 或 Link Binary With Libraries。因为A库生成时仅仅压缩,并没有静态链接,所以这样设置不会报错,只要让编译器可以正常校验通过即可。
2.2 两个动态库
2.2.1 两个动态库包含相同的符号:
- 场景:动态库A、B,其中 A、B 包含同一个类
Obj
,然后将 A、B 同时集成到工程中去。主工程调用[Obj test];
- 结果:运行无异常,启动时控制台会输出一个警告“Class Obj is implemented in both xxx and xxx”。大概意思就是 A.framework 和 B.framework 的可执行文件里面都包含了
Obj
这个类。至于选哪个,取决于linker flag,或者 Link Binary With Libraries 中的先后顺序,先被动态链接的会被调用到。 - 原因:A、B均需要参与主工程的动态链接,仅会符号绑定(bind)一次,所以先绑定的会被调用到。
2.2.2 动态库 A 依赖动态库 B:
- 场景:动态库A、B,其中代码同 2.1.2。A 库在 Framework Search Path 中正确设置 B 库路径、Link Binary With Libraries也需要设置(注意和2.1.2静态库情况的对比)。
- 结果:主工程这里同样只正确引入A,注意动态库需要选 embed。B库不引,或者仅设置Framework Search Path。结果build success,但是程序启动就 crash ,控制台报错“Library not loaded xxx”
- 原因:因为A 依赖 B,所以 B 也会被动态链接。以为编译时仅仅静态链接,所以编译可以正常通过。但是因为B库没有没内嵌,所以启动时动态链接,会报错,不能正确的加载B库。
2.3 一动一静
2.3.1 静态库和动态库包含相同符号:
- 场景:静态库A、动态库B,其中 A、B 包含同一个类
Obj
,然后将 A、B 同时集成到工程中去。主工程调用[Obj test];
- 结果:运行无异常,启动时控制台会输出一个警告“Class Obj is implemented in both xxx and xxx”。主工程中调用,会调入静态库A。动态库B的调用,会调B库自己内部的。
- 原因:因为静态库会在主工程静态链接时,被正确的链接进二进制可执行文件。同样,动态库B也会在生成时将源码生成的 .o 文件正确的静态链接进去。所以最终各自调用各自的。
2.3.2 静态库依赖动态库:
- 场景及结论:静态库A、动态库B,A依赖B。生成A的时候,只需设置 Framework Search Path 即可,因为生成A不需要静态链接,仅仅只是压缩 .o 文件,只需要编译器不报错即可。主工程使用时,需要将 A、B都引入。原因不在赘述,读者自行思考。
2.3.3 动态库依赖静态库:
- 场景及结论:动态库A、静态库B,A依赖B。生成A的时候,不仅要设置 Framework Search Path ,还需要设置 Link Binary With Libraries ,因为A需要静态链接,且会将静态库B吸附。主工程使用时,只需要将 A引入即可。原因不在赘述,读者自行思考。