iOS静态库中类的分类问题和符号冲突问题(Xcode other Link Flags)

原文地址

什么是可执行文件?

要理解静态库我们就得清楚最终可执行文件(.out)的生成过程了

目标文件的生成过程

当我们写的源代码 hello.c 经过上述4个步骤:预处理(Prepressing)、编译(Compilation)、汇编(Assembly)和链接(Linking)后,就生成了我们的可执行文件 a.out 了。
注意:可重定位目标文件是(.o) 而 可执行文件是 (.out),以下文章描述都遵循这种叫法。

当我们明白到什么是可执行文件后,那么再来看看究竟什么是静态库?

静态库定义:

其实一个静态库可以简单地看成一组目标文件(.o)的集合,即很多目标文件经过压缩打包后形成的一个文件。
比如我们在Linux中最常用的C语言静态库libc位于 /usr/lib/libc.a,它属于 glibc 项目的一部分。而 glibc 本身是用C语言开发的,它由成百上千个C语言源代码文件组成,也就是说,编译完成以后有相同数量的目标文件,比如输入输出有 printf.o,scanf.o;文件操作有fread.o,fwrite.o;时间日期有date.o,time.o;内存管理有malloc.o等。把这些零散的目标文件直接提供给库的使用者,很大程度上会造成文件传输、管理和组织方面的不便,于是通常人们使用 ar 压缩程序将这些目标文件压缩到一起,并且对其进行编号和索引,以便于查找和检索,就形成了libc.a这个静态库文件。

例如当我们使用如下代码的时候

#include<stdio.h>
int main(int argc, const char* argv[] )
{
   printf("Hello World");
}

我们先用编译器和汇编器将上述代码生成目标文件 hello.o。

$gcc -c hello.c

示例代码用到了 printf () 这个iO函数,而该函数的符号就位于 printf.o 中,所以我们就需要让链接器链接 printf.o,当然printf.o 也会依赖其他目标文件,而 printf.o 等文件又在 静态库 libc.a 当中,
我们就需要让链接器 (ld) 静态链接到静态库 libc.a 中去寻找示例代码中需要用到的目标文件,并生成目标文件 hello.out。

$ ld -static -e main hello.o /usr/lib/libc.a -o hello.out

我们今天的主题主要在于链接与静态库这一步中


Xcode 中配置链接器(other linker flags)

other linker flags 是 xcode 这个集成开发环境所特有的,目的是让连接器器 ld 除了默认参数外再根添加额外参数进行链接工作。

Object-C 链接特性:

The "selector not recognized" runtime exception occurs due to an issue between the implementation of standard UNIX static libraries, the linker and the dynamic nature of Objective-C. Objective-C does not define linker symbols for each function (or method, in Objective-C) - instead, linker symbols are only generated for each class. If you extend a pre-existing class with categories, the linker does not know to associate the object code of the core class implementation and the category implementation. This prevents objects created in the resulting application from responding to a selector that is defined in the category.

Object-C的链接器并不会为每个方法建立符号表,而是为每个类建立链接符号。这样的话静态库中定义了已存在的类的分类,链接器就以为这个类存在了,不会将分类和核心类代码关联(合并)起来,这样在最后可执行文件中,就会找不到分类里所定义的方法。

例如如下错误:

Snip20170613_4.png

就看 log 可以看出,是 NSString 的一个分类方法 designByOhterLinker 找不到实现了,而这个方法,确实是一个静态库里面的一个分类方法。

Snip20170613_5.png

如何解决这个问题?

三个Linker 参数:

  • -ObjC
  • -all_load
  • -force_load
  • -dead_strip (8.27日更新)
-ObjC :

This flag causes the linker to load every object file in the library that defines an Objective-C class or category. While this option will typically result in a larger executable (due to additional object code loaded into the application), it will allow the successful creation of effective Objective-C static libraries that contain categories on existing classes.

加入这个参数后,链接器会将静态库中的每个类和分类加载到最后的可执行文件,当然,这个参数会导致可执行文件比较大,原因是加载了更多的额外对象的代码到可执行文件当中去,但是这会解决 Objec-C 中静态库中已存在的类包含的分类问题。

上面说得很清楚,-ObjC 会解决静态库中已存在的类的分类问题,那么,如果分类存在与静态库,但是类并不在静态库的这种情况,该怎么办呢?

Important: For 64-bit and iPhone OS applications, there is a linker bug that prevents -ObjC from loading objects files from static libraries that contain only categories and no classes. The workaround is to use the -allload or -forceload flags.

说得很清楚,使用-all_load 或 -force_load 就可以解决上述问题。

-all_load:

该参数把所找到的目标文件都加载到可执行文件当中去,但是这就存在一个问题了,如果两个静态库中,都使用了同一份可重定位的目标文件(这是一个很常见的问题,例如大家的目标文件都使用了用以名字 base64.o)就会发生 ld: duplicate symbol 符号冲突问题,所以不太建议使用。

-force_load:

该参数的作用跟 -all_load 其实是一样的,但是 -force_load 需要指定要进行全部加载的库文件的路径,这样的话,只要完全加载一个库文件,不影响其余库的可重定位目标文件的按需加载。

但是也有一种最头痛,就是当两个静态库中使用了相同的目标文件

Snip20170613_1.png

上图的两个上图的两个 libMyOtherStaticLibrary.a 和 libMyStaticLibrary 中的 MyClass.o 类发生了冲突
那么,这个时候有两种解决方法:

1、利用 -force_load 让链接器指定编译把其中一个静态库的目标文件,不加载另一个静态库的重复目标文件

具体做法:

Snip20170613_2.png

但是这么做有一个弊端,如果这两个静态库同时都使用到了分类(基本上都会使用吧)那么如果只让编译器加载其中一个静态库的目标文件 (-force_load),而不将另一个静态库中的分类合并加载到目标文件的话,也是会导致运行的时候导致上述的崩溃问题。但是如果 -foce_load 两个静态库,又会有符号冲突,那么,怎么办呢?


2、简单来说就是去除某个静态库中的重复目标文件,然后再打包

具体做法:
1)通过使用压缩工具命令 ar -t 去查看两个静态库文件里的目标文件那些存在冲突
如下:

-Snip20170613_7.png
Snip20170613_6.png

很明显就是 MyClass.o 这个目标文件发生符号冲突了, 其实不这样看也行,反正编译的时候 Clang 编译器就就会有符号冲突的报错,上图 Xcode 报错的那个图就是很好的例子,可以看错那些目标文件重复了。

2)将其中一个静态库中的重复目标文件去掉,然后再次打包成静态库使用

  • 首先利用 lipo 命令将其中一个iOS静态库的文件解压出来(因为iOS的静态库文件是一个将不同 CPU 架构静态库合并的一个打包文件)。
-Snip20170613_9.png

可以看出 libMyOtherStaticLibrary.a 中包含了 armv7 跟 arm64 两种架构的静态库文件

  • 分别将两种不同架构的静态库文件提取出来
-Snip20170613_10.png
-Snip20170613_11.png
  • 使用 ar 压缩工具分别将这两个不同架构的静态库文件与另一个发生冲突的静态库文件中的目标文件剔除出去。
-Snip20170613_12.png

通过上面命令看出已成功将 MyClass.o 剔除出静态库

  • 利用 lipo 将两个不同架构的静态库重新打包封装成 iOS 的静态库文件
-Snip20170613_13.png
5cac35f4-a80d-4e91-8d23-137d1d946c47.png

然后在 libMyOtherStaticLibraryOut.a 这个静态库重新放到工程当中去替换原来的 libMyOtherStaticLibrary.a

Other linker flags 只需用 -ObjC 就可以了

编译,Successful!
运行,完美!

-dead_strip (2017.8.27 更新)

参数的作用在于解决我们上面可重定位目标文件(.o)中类符号的冲突问题,如果发生了这种情况,使用该参数就是一个非常快捷的办法了,让 Clang 编译器帮助我们去除重复符号的可重定位目标文件问题。
但是使用这个参数却有一个问题,就是如果我们使用了该参数,就不能使用 -all_load 或 -force_load,因为,如果我们指定了让编译器帮我们决定哪些目标文件该被链接,哪些不被链接(-dead_strip),那么我们就不能手动的强制地让所有目标文件都进行链接了(-all_load 或 -force_load)。如果是这样的话,我们又回到最初的问题了-ObjC 会解决静态库中已存在的类的分类问题,那么,如果分类存在与静态库,但是类并不在静态库的这种情况,该怎么办呢? 显然,对于这种情况,还是得使用上面的方法


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

推荐阅读更多精彩内容