iOS开发 - OC - duplicate symbol _OBJC / undefind symbol 错误的相关处理
前言:
作为一个iOS开发,相信大家都会遇到类似于 “duplicate symbol” 的程序报错。 对于很多新手来说,可能会有点手足无措,因为这种类型的报错一般并非是代码的逻辑错误,大部分情况下是在编译过程出错导致的,因此相对来说排查不易。在前几天,我在引用了两个SDK的过程中出现了这个问题,运用不同的手段最终解决了这个问题。今天本文就这个错误进行一个详细的分析以及如何处理做一个探讨,如果有错误的地方还请指出。
一、duplicate symbol /undefind symbol出现的原因
duplicate symbol错误原因
这种错误的原因多种多样,解决方式因此也并不固定,以下几种情况都会导致高错误的出现。
- 误将.m文件引入为头文件。
- 同一个工程中含有同名的文件。(一般在多人开发过程容易出现)
- 引入的第三方框架包涵了与本地同名的文件
- 第三方框架的.o文件同名
- 引用了不同库文件中含有相同的方法名
undefind symbol错误原因
- 相关的.a文件没有加入。
- 相关的.a文件中缺少对应的方法。
原因分析:
虽然这么多种类的错误原因看似复杂,实际上他们的根源是一样的。 前面说了这是在编译的阶段的错误,我们就从编译器开始分析。
Xcode3所使用的编译器是gcc编译器,而在Xcode4之后和版本中,Xcode所使用的编译器已经替换成为新的LLVM编译器。LLVM编译器的前端是clang,当然就算是现在,Xcode还是支持GCC的,不过可能需要手动下载。编译器的流程大概是下面的图的样子,不过原理大致一样,主要是有于理解,在网上看到的讲解(原文看这里):

预处理: 预处理相当于根据预处理命令组装成新的C程序,不过常以i为扩展名。
编译: 将得到的i文件翻译成汇编代码。s文件。
汇编: 将汇编文件翻译成机器指令,并打包成可重定位目标程序的O文件。该文件是二进制文件,字节编码是机器指令。
链接: 将引用的其他O文件并入到我们程序所在的o文件中,处理得到最终的可执行文件。
虽然这是针对C程序的讲解,但是作为C大家族的OC也是类似。.s文件是汇编文件,貌似Xcode的clang是没有这一步的,而是直接到了.o文件。*.o文件原来就是我们说的目标文件,是二进制的,在很多第三方库中,我们一般会看到.a或者 .framework 或者直接XXSDK, 其实这些文件中包涵的都是.o文件。 不信你可以随便找一个.a文件,然后控制台对其进行 ar -x *.a 指令, 会解压出很多.o文件, 这些都是制作者写的.m文件经过编译得到的,经过打包成了.a文件。 .o文件最终会被用来链接,才可以变成一个可执行的文件,比如iPA文件就是啦。这里要好好弄清楚链接过程了。
“
链接器 (linker) 将一个个的目标文件 ( 或许还会有若干程序库 ) 链接在一起生成一个完整的可执行文件。
在符号解析 (symbol resolution) 阶段,链接器按照所有目标文件和库文件出现在命令行中的顺序从左至右依次扫描它们,在此期间它要维护若干个集合 :
(1) 集合 E 是将被合并到一起组成可执行文件的所有目标文件集合;
(2) 集合 U 是未解析符号 (unresolved symbols ,比如已经被引用但是还未被定义的符号 ) 的集合;
(3) 集合 D 是所有之前已被加入到 E 的目标文件定义的符号集合。一开始, E 、 U 、 D 都是空的。
链接器的工作过程:
(1): 对命令行中的每一个输入文件 f ,链接器确定它是目标文件还是库文件,如果它是目标文件,就把 f 加入到 E ,并把 f 中未解析的符号和已定义的符号分别加入到 U 、 D 集合中,然后处理下一个输入文件。
(2): 如果 f 是一个库文件,链接器会尝试把 U 中的所有未解析符号与 f 中各目标模块定义的符号进行匹配。如果某个目标模块 m 定义了一个 U 中的未解析符号,那么就把 m 加入到E 中,并把 m 中未解析的符号和已定义的符号分别加入到 U 、 D 集合中。不断地对 f 中的所有目标模块重复这个过程直至到达一个不动点 (fixed point) ,此时 U 和 D 不再变化。而那些未加入到 E 中的 f 里的目标模块就被简单地丢弃,链接器继续处理下一输入文件。
(3): 如果处理过程中往 D 加入一个已存在的符号 ,或者当扫描完所有输入文件时 U 非空,链接器报错并停止动作。否则,它把 E 中的所有目标文件合并在一起生成可执行文件。
”
文中看到 “符号”这个字眼,是不是很熟悉(看本文标题),报错指的就是symbol这个东西,它实际上将我们在程序中的全局变量名、函数名或类名,通过OC的消息发送方式,我们大概也联想到了,OC中通过函数名来识别函数进行相应的处理。上文中我门看到有三个集合,D集合就是合法的目标程序的符号集合,也即是我门所说的.o中的全局变量名、函数名或类名都在里面了。我门自己也可以查看这个表格的,方法是在控制台 对.o文件进行
nm *.o >> symbols.txt
比如我这里获取到了cJSON中的符号列表是这样的

最终会生成*.o文件中所有的函数名存储在symbols.txt中。 在编译器链接的过程中,每当获取到一个符号名的时候,都会将制放入表格,如果有同名的则会报错。
现在知道为什么程序报错了吗?
分析结果:
- 误将.m文件引入为头文件。 : 当引入了.m文件,说明同时也引入了.m文件中的.h文件,编译器不管那么多,所有的函数名照单全收,于是重名了,报错!
- 同一个工程中含有同名的文件。(一般在多人开发过程容易出现)
- 引入的第三方框架包涵了与本地同名的文件: 框架中的同名文件中,一般都是开源的,里面含有相同的符号名可以理解了
- 第三方框架的.o文件同名 同上
- 引用了不同库文件中含有相同的方法名 同上
其实这个错误说白了就是定义相同的函数名。
- 相关的.a文件没有加入。 链接列表中没有的符号名 被使用 报错
- 相关的.a文件中缺少对应的方法。 链接列表中没有的函数名 被使用 报错
二、duplicate symbol /undefind symbol错误的解决办法
现在我门知道原因了,那就开始着手解决了。
duplicate symbol解决办法
1.前面三个原因属于项目中的因素,就相对简单了,如果是.m 文件引用错误,改为h文件即可。工程中有同名文件,要不删除,要不改名字。有相通的函数名,要不删除,要不修改。搞定!
2.后两个原因是引用的库文件问题,解决稍微麻烦。
如果是因为第三方框架的引入导致duplicate symbol 错误,那就好好看看提示,一般都会有提示告知,在某个地方含有相同的函数名(下图这样的)。

这里要分下情况,如果是使用了相同的库文件,那直接解开对应的SDK,将相同的半部分删除即可。操作流程如下:
一般的sdk都会介入不同的架构用于不同的平台上的编译,比如如果想在模拟器上编译就需要兼容 i386架构, 真机需要兼容armv7架构等。我门先要将不同的架构的文件分离出来。 上控制台console 指令 查看库文件支持的架构:
$ lipo -info FunSDK
打印出:
/Users/JDiOS/Desktop/嘉德/jadeApp2/jadeApp2/FunSDK.framework/Versions/A/FunSDK are: armv7 armv7s i386 x86_64 arm64
说明该静态库支持的是 armv7 armv7s i386 x86_64 arm64 5个不同的架构。
如果我们要修改的话,需要全部剥离,下面只挑选 armv7架构进行演示:
lipo FunSDK -thin armv7 -output Funrmv7.a
成功之后 可以看一下文件大小的对比,大概是没有分离之前的1/5。 说明静态库.a文件其实只是一个压缩包,将多个架构的文件压缩在一起了。

之前说了.a文件其实是很多.o文件的集合,我门要删除某个.o文件还要再进行分解,这里要提醒一下,先建立好一个文件夹,将获得的armv7架构库文件放进文件夹,在文件夹中处理,不然 .o文件太多,不好管理,丢失了就不好,因为处理完我门还要打包回去。指令:
ar -x Funrmv7.a
可以看到很多 .o文件 。

这时候找到找到要删除的.o 将之删除即可。 也可以指令
rm cJSON.o //删除的是 cJSON.o 文件
删除完毕之后,就该合并回去了。一步一步来,想将所有得.o文件打包回去
libtool -static -o ../new-armv7.a *.o //将所有的o文件打包成new-armv7.a
刚才之将armv7一个架构的文件处理完,要完全做完,其它的架构也要处理。 最终 获得所有的架构的文件合并成静态库 指令
lipo -create -output FunSDKnew armv76.a i386.a arm64.a armv7s.a x86_64.a
最后将FunSDKnew改名 FunSDK(你的项目中的原来的SDK的名字) 覆盖原来项目中的SDK即可。
注意点:
如果是名字相同的当然可以这么处理, 可是,我门已经分析过,.o文件冲突的直接原因不是文件本身,而是文件中包含的符号。如果SDK对cJSON文件的内容进了更改,那么刚才的方法虽然可以避免符号冲突,但是,整个文件被删除,修改的内容也被删了,最终可能导致SDK部分功能没了, 这当然不是我们想看到的。实际上,我遇上的就是这个问题, 最后编译过程中,因为直接删除了FunSDK中的文件,导致符号缺失 ,像这样:

是不是看到什么了,没错,也是本文说的一个点 undefind symbol 错误。
原来虽然cJSON是开源的,但是FunSDK 在开源的基础上做了修改,直接删除cJSON等于把FunSDK新增的setStringValue的函数给删掉了,预编译器又报错! 怎么办?
请看:
undefind symbol 解决办法
两个办法:
- 找SDK供应方 要.o文件的源代码,修改之后打包回去 (呵呵,源代码有点难啊)。 最好还是跟供应商协商修改。
- 一个取巧的办法 (这是我无意中发现的) 一般SDK有冲突的文件大部分原因是因为打包相同的开源文件,去网上都可down的到,下载过来,将里面的缺失的方法(符号)添加,放到项目中,这样可以覆盖库中的方法。 但是有一定的风险,运气好可能可以避免,因为你的项目中不一定会用到SDK中所有的方法,恰巧你库中缺失的方法就是不需要用到的,那么只需要写入个方法,不需要实现什么,编译过程可以通过。 解决!
- 缺失库文件嘛,看看是不是target -> bulid Phases 中加入对应的.a 文件(主要还是这个原因吧)
三、其它的方法 解决duplicate symbol问题
是不是每次出现这种问题都需要进行上上述的方案来解决呢? 不一定。
我门已经知道这是在编译链接的时候出现的错误,Xcode在链接的时候,提供了一个可选的链接方式。
项目中: target -> bulid setting -> link -> oteher link flags
这里可以使用的库链接方式有: -all_load -Objc -force_load -dead_strip 等等。
为了解决duplicate symbol的问题,以下链接方式可以使用:
- 设置-dead_strip之后将会是项目忽略掉重复的符号, 最终编译会通过的,但是如果就像之前所说的,忽略的东西如果被修改过,有部分可能导致SDK的部分API不能使用。
- -force_load也可以通过编译,这种方式是强制链接静态库。 需要指定链接的库文件
在大部分情况下,使用-dead_strip 和 -force_load链接方式是可行的,毕竟比上面的方法要轻松无数倍不是吗?如果没什么特别的要求,建议使用这个方式。
附一张链接方式的含义。

iOS开发 - OC - duplicate symbol _OBJC / undefind symbol 错误的相关处理的更多相关文章
- iOS开发OC基础:Xcode中常见英文总结,OC常见英文错误
在开发的过程中难免会遇到很多的错误,可是当看到系统给出的英文时,又不知道是什么意思.所以这篇文章总结了Xcode中常见的一些英文单词及词组,可以帮助初学的人快速了解给出的提示.多练习,就肯定能基本掌握 ...
- iOS开发——OC篇&OC高级语法
iOS开发高级语法之分类,拓展,协议,代码块详解 一:分类 什么是分类Category? 分类就是类的补充和扩展部分 补充和扩展的每个部分就是分类 分类本质上是类的一部分 分类的定义 分类也是以代码的 ...
- iOS开发-OC语言 (一)oc数据类型
分享一套以前学习iOS开发时学习整理的资料,后面整套持续更新: oc数据类型 数据类型:基本数据类型.指针数据类型 基本数据类型:数值型.字符型(char).布尔型.空类型(void) 指针数据类型: ...
- iOS开发——OC篇&常用关键字的使用与区别
copy,assign,strong,retain,weak,readonly,readwrite,nonatomic,atomic,unsafe_unretained的使用与区别 最近在学习iOS的 ...
- iOS开发——OC篇&消息传递机制(KVO/NOtification/Block/代理/Target-Action)
iOS开发中消息传递机制(KVO/NOtification/Block/代理/Target-Action) 今晚看到了一篇好的文章,所以就搬过来了,方便自己以后学习 虽然这一期的主题是关于Fou ...
- iOS开发——OC篇&纯代码退出键盘
关于iOS开发中键盘的退出,其实方法有很多中,而且笔者也也学会了不少,包括各种非纯代码界面的退出. 但是最近开始着手项目的时候却闷了,因为太多了,笔者确实知道有很多中方法能实现,而且令我影响最深的就是 ...
- iOS开发- "duplicate symbol for architecture i386" 解决的方法
今天整合项目的时候, 遇到了这样一个问题. duplicate symbol _flag in: /Users/apple/Library/Developer/Xcode/DerivedData/bl ...
- iOS开发——OC基础-ARC、BLOCK、协议
一.ARC ARC 是一种编译器特性!而不是IOS运行时特性,和JAVA中得垃圾回收机制完全不一样ARC是自iOS 5之后增加的新特性,完全消除了手动管理内存的烦琐,编译器会自动在适当的地方插入适当的 ...
- iOS开发-OC语言 (四)数组
知识点 1.NSArray 2.NSMutableArray 1.数组的基本用法: 2.数组的遍历 3.数组排序 =========== NSArray 不可变数组 ============= ...
- iOS开发-OC数据类型
以下是OC中的实例,Swift部分不适用 iOS中的注释 // 单行注释 // 注释对代码起到解释说明的作用,注释是给程序员看的,不参与程序运行 /* 多行注释 Xcode快捷键 全选 cm ...
随机推荐
- 【并查集】【树】最近公共祖先LCA-Tarjan算法
最近公共祖先LCA 双链BT 如果每个结点都有一个指针指向它的父结点,于是我们可以从任何一个结点出发,得到一个到达树根结点的单向链表.因此这个问题转换为两个单向链表的第一个公共结点(先分别遍历两个链表 ...
- 给自己~~微语&&歌单
如果你很忙,除了你真的很重要以外,更可能的原因是:你很弱,你没有什么更好的事情去做,你生活太差不得不努力来弥补,或者你装作很忙,让自己显得很重要.——史蒂夫-乔布斯 时间并不会因为你的迷茫和迟疑而停留 ...
- iOS开发之APP上线
APP 上线有两种途径: 一种是 Xcode->openDeveloperTool->applicationLoader,这种打开后登陆appleID就可以选取并且交付您的应用程序了.这种 ...
- getEnhancedMicrophone 方法
[转]http://www.cnblogs.com/iBlogger/archive/2011/11/16/2251847.html Flex 4.6 SDK 提供了 getEnhancedMicro ...
- Flex 利用Space控制进行组件的右对齐
Spacer 控件可帮助您布置父容器中的子项.虽然 Spacer 控件不会绘制任何内容,但它会在父容器中为其本身分配空间. 在以下示例中,使用灵活的 Spacer 控件将 Button 控件推到右侧, ...
- C#的循环语句
1.输入月份,日期号,输出是见年的第几天. 循环语句: for 格式 for(初始条件;循环条件;状态改变) { 循环体,执行代码(break;跳出循环体) } 2.一个游戏,前20关是每一关自身的分 ...
- javascript函数一共可分为五类: ·常规函数 ·数组函数 ·日期函数 ·数学函数 ·字符串函数
javascript函数一共可分为五类: ·常规函数 ·数组函数 ·日期函数 ·数学函数 ·字符串函数 1.常规函数 javascript常规函数包括以下9个 ...
- X86 Socket 通信
struct txd_socket_handler_t { int fd; }; txd_socket_handler_t *txd_tcp_socket_create() { txd_socket_ ...
- centos6.3环境下升级python及MySQLdb的安装
近来突然想鼓捣下linux下的python,看下Python数据库方面的东西,想着在centos下测试下.然而安装的过程有很多坑.下面对整个流程进行下记录 1.python基本库的安装 在安装pyth ...
- Oracle用法集锦
查询第一条数据 修改表名 ALTER TABLE tablename RENAME TO newtablename 修改列名: ALTER TABLE BD_PRI RENAME COLUMN EU_ ...