iOS符号冲突(duplicate symbols)

前言

背景是我们项目升级某个SDK,结果发现项目和SDK出现符号冲突。
符号冲突是接入SDK有可能会出现的问题,本文便尝试从技术角度去解决。

正文

因为本身项目本身不便公开,所以新建两个工程来模拟这个场景。(工程代码地址)
LYTestFramework静态库工程,里面带有不公开的SSUser类,模拟SDK;
LearnSymbol普通工程,模拟项目的主工程,里面也有SSUser类;

将LYTestFramework手动导入LearnSymbol工程:

这样便出现了两个SSUser:
一个是LYTestFramework.framework内不公开的SSUser;

@implementation SSUser
// from framework's user
- (void)test {
    NSLog(@"framework test");
}
@end

另一个是LearnSymbol工程内自己带的SSUser;

@implementation SSUser
// from project's user
- (void)test {
    NSLog(@"main test");
}
@end

那么编译的时候,就会出现duplicate symbol _OBJC_CLASS_$_SSUser的错误。

可是,当我真正开始运行的时候,才发现竟然编译通过了:

对比了这个新建工程和原工程的Other Linker Flags,发现是因为新工程少了一个-ObjC的设置,另外原工程还有-l secXXX的flag。

回顾下-ObjC 、 -all_load 、-force_load这三个flag的区别:

  • -ObjC 链接器会加载静态库中所有的Objective-C类和Category;(导致可执行文件变大)
  • -all_load 链接器会加载静态库中所有的Objective-C类和Category(这里和上面一样);当静态库只有Category时-ObjC会失效,需要使用这个flag;
  • -force_load 加载特定静态库的全部类,与-all_load类似但是只限定于特定静态库,所以-force_load需要指定静态库;当两个静态库存在同样的符号时,使用-all_load会出现duplicate symbol的错误,此时可以选择将其中一个库-force_load;(需要注意两个库的版本是不是一致的)

所以这里的直接编译通过的原因:工程中已经有了SSUser类的符号,所以链接的时候会直接使用工程中的SSUser符号,所以编译运行完的结果是调用了工程中的SSUser类,静态库中的SSUser并没有被链接。

而原工程的-l secXXX的链接flag是什么意思?
gcc有三个很像的参数,分别是-l -l -L,第一个I是i的大写,中间的是L的小写l。

  • -I,用于指定头文件的地址;
  • -l,用于指定具体的静态库、动态库;
  • -L,用于指定库文件的地址;

回到我们的工程,我们往Other Linker Flags添加-ObjC的flag之后,再次尝试编译。

此时终于复现了之前的符号冲突:

duplicate symbol _OBJC_CLASS_$_SSUser in:
    /Users/loyinglin/Library/Developer/Xcode/DerivedData/LearnSymbol-dhlwaeprifzeedegywrvodujmcoj/Build/Intermediates.noindex/LearnSymbol.build/Debug-iphonesimulator/LearnSymbol.build/Objects-normal/x86_64/SSUser.o
    /Users/loyinglin/Documents/Learn/LearnDuplicateSymbol/LearnSymbol/LearnSymbol/LYTestFramework.framework/LYTestFramework(SSUser.o)
duplicate symbol _OBJC_METACLASS_$_SSUser in:
    /Users/loyinglin/Library/Developer/Xcode/DerivedData/LearnSymbol-dhlwaeprifzeedegywrvodujmcoj/Build/Intermediates.noindex/LearnSymbol.build/Debug-iphonesimulator/LearnSymbol.build/Objects-normal/x86_64/SSUser.o
    /Users/loyinglin/Documents/Learn/LearnDuplicateSymbol/LearnSymbol/LearnSymbol/LYTestFramework.framework/LYTestFramework(SSUser.o)
ld: 2 duplicate symbols for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

接下来从尝试技术的角度去解决这个问题:
解决方案1:去掉主工程的SSUser,用静态库里面的SSUser;

不可行,静态库的SSUser没有开放头文件,无法使用;

解决方案2:在主工程的compile source去掉SSUser.m文件,工程中仅用SSUser.h文件来调用;(假设两方用的是同个版本)
尝试编译,符号冲突可以解决;
运行的结果表示调用了LYTestFramework中的SSUser:

2019-07-14 14:13:21.767218+0800 LearnSymbol[28982:5102302] framework test

解决方案3:去掉LYTestFramework静态库中的SSUser符号,链接时全部使用主工程的SSUser;
我们知道静态库是多个.o文件组成的,那么我们可以找到SSUser.o然后剔除,静态库依赖的SSUser会在链接时找到主工程生成的SSUser.o;
我们先进入打包的出来的LYTestFramework.framework文件夹,目录如下:

我们在Headers的同级目录创建一个目录pack,将LYTestFramework这个文件移动到pack目录中。
ar -t LYTestFramework指令,可以看到这个库中的.o文件包括SSUser.o,下面尝试手动移除这个SSUser.o文件:

  • 1、先将LYTestFramework解压:ar xv LYTestFramework
  • 2、手动删除SSUser.o文件;
  • 3、回到上级目录,重新把.o文件打包:ar rcs LYTestFramework pack/*.o

再用ar -t LYTestFramework指令查看,发现SSUser.o已经不见,重新打包成功运行,结果表示调用了主工程的SSUser:

2019-07-17 16:20:33.576468+0800 LearnSymbol[86290:7683465] main test

附1:这为了简化逻辑,这里只有模拟器的cpu架构,没有包括armv7/arm64,用 lipo -info LYTestFramework指令可以看到:
LYTestFramework is architecture: x86_64;
如果有多种cpu架构,需要分别对每种架构进行处理,再合并。

附2:以上的解决方案均是假设两方用的是同个静态库版本。如果是不同版本,则需要修改命名,使得多个版本的静态库可以共存。

另一个Linking中的选项:

Dead Code Stripping 是对程序编译出的可执行二进制文件中没有被实际使用的代码进行Strip操作。
Dead code stripping removes code that the compiler determines is unreachable.
代码举例:

总结

符号冲突是引入第三方库的时候,有可能会遇到的问题。
当库A和库B的符号出现冲突时,如果库A和库B冲突的符号,是功能相同的符号,则可以选择去掉其中一个符号,选择只加载其中一个库的符号。
如果两个符号所表示的意义不同,比如说不来自同一个库(仅仅是命名一样,导致符号冲突),或者来自同一个库但是版本不同,这种只能通过重命名或者修改库的代码逻辑来实现共存。

附录

静态库与动态库的思考
编译与链接过程的思考
https://blog.csdn.net/djl4104804/article/details/43099061
https://garbageout.wordpress.com/2015/03/24/dead-code-stripping/

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

推荐阅读更多精彩内容