符号冲突本质是链接器遇到多个同名全局定义(如_t、_objc_class_等),根源在重复编译的.o文件或静态库,而非动态库本身;需通过nm定位冲突符号,删冗余定义、加static/extern或修正#import ".m"错误。

直接结论:不是动态库本身“出现” duplicate symbol,而是你链接时把多个定义了同名全局符号的目标文件(.o)或静态库(.a)一起塞给了 linker —— 动态库(.dylib)只导出符号,不参与重复定义检查;真正冲突的,是编译阶段生成的多个 .o 文件,或静态库之间、静态库与主工程之间的符号重叠。
看报错里重复的是什么符号类型
报错行里最关键的不是 “duplicate symbol”,而是符号前缀和后缀:
-
_OBJC_CLASS_$_MyViewController或_OBJC_METACLASS_$_MyViewController→ 类名冲突,大概率是两个.m文件都实现了同名类,或误#import "MyViewController.m" -
_g_userConfig或_kDefaultTimeout→ 全局变量/常量重复定义,常见于多个.m文件里写了const int g_userConfig = 10;或NSString * const kDefaultTimeout = @"30s"; -
_myHelperFunction→ 普通 C 函数名重复,比如两个.c文件都写了void myHelperFunction() { ... }(没加static)
符号前缀直接暴露语言层和作用域,先盯住它,比盲目 clean rebuild 有效得多。
检查 .m 文件是否被 #import 进其他 .m
这是 iOS/macOS 项目里最隐蔽也最高频的根源。Xcode 不会警告你 #import "SomeClass.m",但 linker 会当场崩溃。
- 打开报错提到的两个
.o文件对应源码(比如ClassA.o和ClassB.o),逐个搜索#import行 - 重点查有没有
#import "XXX.m"—— 尤其是第三方封装、模板代码或旧项目迁移时容易残留 - 如果发现,立刻改成
#import "XXX.h";若必须用实现细节,改用@class前向声明 + 在.m里#import
注意:.h 里写 extern NSString * const MyKey; 是安全的;但 .h 里写 NSString * const MyKey = @"xxx"; 就会导致每个 #import 它的 .m 都生成一份定义 —— 这种写法必须删掉。
区分 static 库和 dynamic 库的符号行为
动态库(.dylib)本身不会导致 duplicate symbol,但它可能“带进来”冲突:
- 如果你链接了两个静态库(
A.a和B.a),而它们内部都含libcommon.o且定义了同名_parse_json,linker 会报错 - 如果你把一个动态库和主工程同时定义了
extern int g_version;,且主工程的.m里写了int g_version = 1;,那 linker 仍会冲突 —— 因为动态库的符号在链接期被解析,不是运行期才加载 - 验证方法:用
nm -U libA.dylib看它导出了哪些符号;用nm -g libB.a | grep parse_json看静态库是否重复提供
真正要动刀的地方,永远是那些被编译成 .o 的源文件,而不是 dylib 文件本身。
clean 并不总能解决问题
DerivedData 里残留的 .o 文件可能已损坏或未更新,但更常见的是问题根本没改 —— clean 只是清缓存,不是修 bug。
- 执行
rm -rf "$(getconf DARWIN_USER_CACHE_DIR)/org.llvm.clang/ModuleCache"清模块缓存(尤其涉及 modulemap 时) - 手动删 DerivedData 对应项目的整个文件夹(Xcode → Preferences → Locations → arrow icon beside “Derived Data”)
- 但最关键的是:改完代码后,确保 Xcode 实际重新编译了相关
.m文件 —— 看控制台是否有Compiling ClassA.m日志,而不是跳过
符号冲突的本质是链接器看到两个以上同名的全局定义,它没法猜你要留哪一个。所以修复动作永远落在“删一个定义”或“改成 static/extern”上,而不是靠工具自动裁决。











