Xcode工程编译之duplicate symbol问题引发的一些知识
概括:
- 文件中重复定义了一个函数、变量(比如全局变量)
- 工程中包含同名的文件。
- 1 在使用import 引入头文件时,由于疏忽,误引入.m 文件。
- 2 同名文件放在不同的文件夹下。
- 3.在 Targets 的 Build Phrases 设置里,查看下 Complie Sources这一项,看看出现问题的类是不是重复的.如果是重复的,删除掉重新添加也能解决这个问题.
- 4.文件里面使用C语言定义的全局变量名或是函数名,在导入的时候因为重复产生了冲突。
比如:在.m 文件的函数如果是用 C 语言的形式写的时候,程序在编译的时候,只看文件名,而不在乎你后面有多少参数,如果是文件名相同的话也是不行的.
1234a.m中
void
drawCircle()
b.m中
void
drawCircle(CGContextRef ctx,
int
radius, CGFloat centerX, CGFloat centerY)
引入.a产生冲突
参考文章1:duplicate symbol问题解决方法
参考文章2: iOS 第三方库冲突的处理
参考文章3:iOS 解决一个因三方静态库冲突产生的duplicate symbol的问题
1. 如果是两个静态库冲突的话,可以将两个.a静态库解压,删除其中一个里面重复的.o文件(编译时产生的临时文件),然后用lipo命令合并两个静态库;比如libx.a文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 1 创建临时文件夹,用于存放armv7平台解压后的.o文件: mkdir armv7 2 取出armv7平台的包: lipo libx.a -thin armv7 -output armv7/libx-armv7.a 3 查看库中所包含的文件列表: ar -t armv7/libx-armv7.a 4 进入armv7文件夹,并解压出object file(即.o后缀文件): cd armv7 && ar xv libx-armv7.a 5 找到冲突的包(比如 JSONKit),删除掉 rm JSONKit.o 6 重新打包,生成libx-armv7.a object file:cd .. && ar rcs libx-armv7.a armv7/*.o,可以再次使用[3]中命令确认是否已成功将文件去除 7 将其他几个平台(armv7s, i386)包逐一做上述[1-6]操作 8 重新合并为fat file的.a文件libMiPushSDK- new .a: lipo -create libx-armv7.a libx-armv7s.a libx-i386.a -output libMiPushSDK- new .a 9 拷贝到项目中覆盖源文件: cp libMiPushSDK- new .a /Users/tony/Desktop/XXXProject/Lib/libMiPushSDK.a |
1 2 3 4 5 6 | -t 显示库文件中所包含的文件 -x 自库文件中取出成员文件 -v 程序执行时显示详细的信息 -r 将文件插入库文件中 -c 建立库文件 -s 若库文件中包含了对象模式,可利用此参数建立备存文件的符号表。 |
2.工程文件和静态库冲突的话,报错会显示XXX.o文件。
1 2 3 4 5 6 7 | 1 Build Phrase里面搜索这个类名 2 显示出来的那几个 .m文件给remove掉就OK 或者(不建议下边这样) 1 在Xcode左侧,工程文件目录结构中找到.m文件 2 中并将Target Membership的勾选去掉,效果一样 注意: 对工程Add Files to一份拷贝的文件夹之后,默认选中了Target Membership。所以在工程目录的结构简单的整理一下后,有时会出现这种错误。 |
拓展
1 拓展:
1 2 | lipo的一些解释: lipo 是一个在 Mac OS X 中处理通用程序(Universal Binaries)的工具。现在发售或者提供下载的许多(几乎所有)程序都打上了“Universal”标志,意味着它们同时具有 PowerPC 和 Intel 芯片能够处理的代码。不过既然你可能不在意其中的一个,你就能够使用 lipo 来给你的程序“瘦身” |
1.1 ios中合成静态库:
将模拟器和设备的静态库文件合并成一个文件输出了,以后在发布可以库的时候不用发一个模拟器版的和一个真机版的了,这样子的一个库可以在编译的时候自动识别需要连接的库
1 | lipo –create Release-iphoneos/libiphone.a Debig-iphonesimulator/libiphone.a –output libiphone.a |
1.2 查看A.a中的.m文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 看一下该文件包含几种arch 输入:file A.a 输出: A.a: Mach-O universal binary with 2 architectures A.a ( for architecture armv7): current ar archive random library A.a ( for architecture arm64): current ar archive random library 结论: 可以看到该文件包含两种arch,分别是armv7和arm64。 2 由于下面抽离object的时候必须是要单一的库,所以这里我们抽出armv7并命名为v7.a: 输入:lipo A.a -thin armv7 -output v7.a 输出:生成一个v7.a文件。 结论:目录下多了一个v7.a文件 3 抽离.a文件的object 输入:ar -x v7.a 输出:生成一些.o文件 结论:目录下多出一些.o文件 4 获取文件,比如:View.o文件 输入:nm View.o > view.m 输出:生成view.m文件 结论:可以查看view.m文件 |
2 拓展:
1 2 3 4 | Target membership是指XCode中,一个文件属于哪一个工程,在XCode左侧的工程面板中选中一个文件,在XCode右侧的属性面板中会显示其Target Membership。 注意: 以前遇到一个错误,就是UIImage创建的时候返回 nil ,仔细查看发现,图片的Target Membership选项没有勾上。这个错误比较难以发现,特此记之。 |
3 拓展:
项目编译时的链接方式
3.1 编译过程:
从C代码到可执行文件经历的步骤是:源代码 > 预处理器 > 编译器 > 汇编器 > 机器码 > 链接器 > 可执行文件
1 2 | 在最后一步需要把.o文件和C语言运行库链接起来,这时候需要用到ld命令。源文件经过一系列处理以后,会生成对应的.obj文件,然后一个项目必然会有许多.obj文件,并且这些文件之间会有各种各样的联系,例如函数调用。链接器做的事就是把这些目标文件和所用的一些库链接在一起形成一个完整的可执行文件。 Other linker flags设置的值实际上就是ld命令执行时后面所加的参数 |
3.2 Xcode里-ObjC, -all_load, -force_load
下面逐个介绍3个常用参数:
1 2 3 4 5 | -ObjC:加了这个参数后,链接器就会把静态库中所有的Objective-C类和分类都加载到最后的可执行文件中 -all_load:会让链接器把所有找到的目标文件都加载到可执行文件中,但是千万不要随便使用这个参数!假如你使用了不止一个静态库文件,然后又使用了这个参数,那么你很有可能会遇到ld: duplicate symbol错误,因为不同的库文件里面可能会有相同的目标文件,所以建议在遇到-ObjC失效的情况下使用-force_load参数。 -force_load:所做的事情跟-all_load其实是一样的,但是-force_load需要指定要进行全部加载的库文件的路径,这样的话,你就只是完全加载了一个库文件,不影响其余库文件的按需加载。 |
引用.a产生“selector not recognized”错误的原因:
1 2 3 | 1. Unix的标准静态库实现和Objective-C的动态特性之间有一些冲突:Objective-C没有为每个函数(或者方法)定义链接符号,它只为每个类创建链接符号。这样当在一个静态库中使用类别来扩展已有类的时候,链接器不知道如何把类原有的方法和类别中的方法整合起来,就会导致你调用类别中的方法时,出现 "selector not recognized" ,也就是找不到方法定义的错误。为了解决这个问题,引入了-ObjC标志,它的作用就是将静态库中所有的和对象相关的文件都加载进来。 2. 本来这样就可以解决问题了,不过在64位的Mac系统或者iOS系统下,链接器有一个bug,会导致只包含有类别的静态库无法使用-ObjC标志来加载文件。变通方法是使用-all_load 或者-force_load标志,它们的作用都是加载静态库中所有文件,不过all_load作用于所有的库,而-force_load后面必须要指定具体的文件。 |
4 拓展:
什么时候该用@class,什么时候该用#import进行声明
1 2 3 4 5 | 1.一般如果有继承关系的用#import,如B是A的子类那么在B中声明A时用#import 2. 另外就是如果有循环依赖关系,如:A->B,B->A这样相互依赖时,如果在两个文件的头文件中用#import分别声明对方,那么就会出现头文件循环利用的错误,这时在头文件中用 @class 声明就不会出错 3.还有就是自定义代理的时候,如果在头文件中想声明代理的话如 @interface SecondViewController:UIViewController时应用#import不然的话会出错误,注意XXXXDelegate是自定义的 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)