如何解决 Cocoa touch 框架中的符号名称冲突

Posted

技术标签:

【中文标题】如何解决 Cocoa touch 框架中的符号名称冲突【英文标题】:How to resolve symbol name conflict in a Cocoa touch framework 【发布时间】:2015-08-03 07:06:26 【问题描述】:

我开发了一个 Cocoa 触控框架,但我遇到了嵌入其中的第三方静态框架类的问题。

当消费者项目使用我的框架并导入我的框架使用的第三方静态框架时,问题是符号冲突。

我最终想从我的框架中删除这些类,因为它们与宿主项目类冲突(它们使用相同的第三方框架)并以某种方式告诉我的框架依赖于主项目第三方框架(我将指示开发人员导入框架), 或者,我将为这些类添加一个前缀,以便在托管项目时嵌入我的框架并使用与我自己的框架相同的第三方框架,它不会出现符号冲突

欢迎任何帮助或指导!

【问题讨论】:

otool 可能会向您显示符号;我不确定是否要更改它们。因此我无法提供答案。 @Tommy(我更新了问题)谢谢,您知道解决该符号冲突问题的其他方法吗? 如果您的项目需要外部框架,有两种方法可以解决它 - 使用包管理器来处理依赖项(例如 CocoaPods)或让用户单独下载依赖项(基本上框架的用户将拥有进行链接并确保没有链接两次)。 @Sulthan 关于指示用户下载依赖项对我来说很好,但问题是我的框架如果没有链接所需的框架就不会构建(它失败说模块 x.framework不见了) 【参考方案1】:

CocoaPods 可以帮助您解决重复符号的问题。 下面我详细解释了如何实现它:

定义 让我们为更简单的解释做一些定义:MyFramework - 您正在开发的框架。MyApplication - 使用 MyFramework 的应用程序。OtherFramework - 使用的第三方框架MyFrameworkMyApplication

问题 据我了解,问题是 Xcode 无法在 OtherFramework 中构建“重复符号”错误。

解决方案 以下是解决该问题需要满足的条件:

1) MyFramework 必须通过 CocoaPods 引用 OtherFramework

// MyFramework Podfile
use_frameworks!
pod "OtherFramework"

2) MyApplication 必须通过 CocoaPods 引用 OtherFramework

// MyApplication Podfile
use_frameworks!
pod "OtherFramework"

3) MyApplication 可以使用任何机制引用MyFramework(通过 CocoaPods 或通过拖放框架进行项目)。

4) OtherFramework 必须使用 CocoaPods 构建。 如果还没有使用 CocoaPods 构建,您可以自己制作。 为此,您需要创建 OtherFramework.podspec 并可选择将其提交到 CocoaPods 私有存储库。如果您有源文件或只有OtherFramework.framework 捆绑包,都没有关系。更多关于构建 CocoaPod 的细节here.

【讨论】:

虽然您的解决方案可能对我有用,但我不能强迫用户使用可可豆荚来引用“otherFramework”。我可以告诉他拖放但不能强制使用 CocoaPods。我正在开发一个 SDK,并希望用户尽可能轻松地使用它。你用 CocoaPods 构建的“4”是什么意思?你的意思是已经作为一个豆荚存在?我将 GoogleMobileAds 视为“其他框架” @MichaelA,你知道吗,我刚刚验证过,第二点不是必需的。所以MyApplication 可以通过任何机制引用OtherFramework。所以你不需要强迫你的客户为GoogleMobileAds 使用 CocoaPods。关于第 4 点,是的,您的框架 MyFramework 必须通过 CocoaPods 引用 GoogleMobileAds。我验证过,它是用 CocoaPods 构建的。可在此处获得:cocoapods.org/pods/GoogleMobileAds。如果不清楚我的意思,请告诉我。 @MichaelA,所以在我的回答中最重要的一点是第一点:MyFramework 必须引用 CocoaPods 的 OtherFramework。 谢谢。我试过了(请参阅 MyFramework 中 CocoaPods 的“OtherFramework”),但它在运行时会出现重复符号错误。请注意,只有当 MyApplication 实际使用“OtherFramework”的类时它才会给出错误,如果它只是链接它而不使用它就不会显示错误 @MichaelA,这很奇怪。您是否有一些示例项目可以重现此问题?因为在我的情况下它工作正常,我还在MyFrameworkMyApplication 中使用OtherFramework 的函数。【参考方案2】:

TL;DR

使用动态框架,您不必太在意它,因为链接器使用合理的默认行为。如果您真的想按照您的要求做,您可以指示链接器这样做,但有运行时失败的风险。请参阅答案末尾的解释。

一般来说,关于静态链接

这是经典“依赖地狱”问题的另一个版本。理论上,静态链接目标文件有两种解决方案:

    说明您的依赖关系,并让您的框架的用户在他们的构建系统中解决它。一些想法:

    CocoaPods 和 Carthage 等外部系统将帮助您解决强制约束用户构建系统的不利因素(即用户可能不使用 CocoaPods)。

    包括依赖项及其头文件,指示您的用户不要使用您提供的该依赖项的版本。当然,缺点是您的用户无法根据需要切换实现。

    (也许是最简单的)。构建你的框架的两个版本,一个没有链接依赖库。然后用户可以选择是使用你提供的版本还是他们自己的版本。缺点是没有很好的方法来确定它们的版本是否与您的代码兼容。

    避免泄漏您的依赖项并将其封装在您的框架中,以牺牲更大的代码大小为代价。这通常是我现在的首选路径,因为即使在移动设备上代码大小也不是真正的问题。方法包括:

    将依赖项作为源文件包含在您的项目中。使用 #define 宏重命名符号(或仅使用 static 符号)。 从源代码构建依赖项,在代码预构建(或一次性)步骤中重命名符号。 按原样构建所有内容,然后重命名构建库中的“内部”符号。这可能会导致错误,特别是因为 swift/obj-c 具有动态方面。例如,请参阅 this question。

This post 讨论了不同替代方案的一些优缺点。

使用动态框架时

如果您要构建动态框架,最佳做法是执行“2”。多于。具体来说,动态链接将防止重复符号问题,因为您的库可以链接到其版本的第三方库,而不管任何客户端使用的库。

这是一个简单的例子(为了简单起见,使用 C,但应该适用于 Swift、ObjC、C++ 或任何使用 ld 链接的东西):

还请注意,我假设您的第三方库是用 C/objC/C++ 编写的,因为 Swift 类 (AFAIK) 不能存在于静态库中。

myapp.c

#include <stdio.h>

void main() 
  printf("Hello from app\n");

  third_party_func();

  my_dylib_func();

mylib.c

#include <stdio.h>

void my_dylib_func() 
  printf("Now in dylib\n");
  third_party_func();
  printf("Now exiting dylib\n");

thirdparty_v1.c

#include <stdio.h>

void third_party_func() 
  printf("Third party func, v1\n");

thirdparty_v2.c

#include <stdio.h>

void third_party_func() 
  printf("Third party func, v2\n");

现在,让我们先编译文件:

$ clang -c *.c
$ ls *.o
myapp.o         mylib.o         thirdparty_v1.o thirdparty_v2.o

接下来,生成静态库和动态库

$ ar -rcs libmystatic.a mylib.o thirdparty_v1.o
$ ld -dylib mylib.o thirdparty_v1.o -lc -o libmydynamic.dylib
$ ls libmy* | xargs file
libmydynamic.dylib: Mach-O 64-bit dynamically linked shared library x86_64
libmystatic.a:      current ar archive random library

现在,如果我们使用(隐式)提供的实现进行静态编译:

clang -omyapp myapp.o -L. -lmystatic thirdparty_v2.o && ./myapp
Hello from app
Third party func, v1
Now in dylib
Third party func, v1
Now exiting dylib

现在,这让我感到非常惊讶,因为我预计会出现“重复符号”错误。事实证明,OSX 上的ld 默默地替换了符号,导致用户的应用程序替换了我库中的符号。原因记录在 ld 手册页中:

ld 只会在需要解析某些符号引用时从静态库中提取 .o 文件

这将对应于上面的第 1 点。 (附带说明,在 Linux 上运行上述示例肯定会出现“重复符号”错误)。

现在,让我们像您的示例一样动态链接:

clang -omyapp myapp.o -L. -lmydynamic thirdparty_v2.o && ./myapp
Hello from app
Third party func, v2
Now in dylib
Third party func, v1
Now exiting dylib

如您所见,您的动态库现在引用静态库的版本 (v1),而应用程序本身将使用另一个 (v2) 版本。 这可能是您想要的,这是默认设置。原因当然是现在有两个二进制文件,每个都有自己的一组符号。如果我们检查.dylib,我们可以看到它仍然导出第三方库:

$ nm libmydynamic.dylib 
0000000000000ec0 T _my_dylib_func
                 U _printf
0000000000000f00 T _third_party_func
                 U dyld_stub_binder

当然,如果我们不链接到应用程序中的静态库,链接器会在 .dylib 中找到符号:

$ clang -omyapp myapp.o -L. -lmydynamic  && ./myapp
Hello from app
Third party func, v1
Now in dylib
Third party func, v1
Now exiting dylib

现在,如果我们不想向使用某个静态库的应用程序公开,我们可以通过不导出其符号来隐藏它(隐藏它是为了不让应用程序意外引用它,而不是真正隐藏它) :

$ ld -dylib mylib.o -unexported_symbol '_third_party_*' thirdparty_v1.o -lc -o libmydynamic_no3p.dylib
$ nm -A libmydyn*
...
libmydynamic.dylib: 0000000000000f00 T _third_party_func
libmydynamic_no3p.dylib: 0000000000000f00 t _third_party_func 

(大写的T表示符号是公开的,小写的t表示符号是私有的)。

让我们再试试最后一个例子:

$ clang -omyapp myapp.o -L. -lmydynamic_no3p  && ./myapp
Undefined symbols for architecture x86_64:
  "_third_party_func", referenced from:
      _main in myapp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

现在我们已经成功地将我们的第三方静态框架隐藏到客户端应用程序中。请注意,通常您不必在意。

如果你真的想要 1. 在动态框架中怎么办

例如,您的库可能需要您的客户端提供的第三方库的确切版本。

当然也有一个链接器标志:-undefined dynamic_lookup

$ ld -dylib mylib.o -undefined dynamic -lc -o libmydynamic_undef.dylib
$ clang -omyapp myapp.o -L. -lmydynamic_undef thirdparty_v2.o && ./myapp
Hello from app
Third party func, v2
Now in dylib
Third party func, v2
Now exiting dylib

当然,如果您的客户端未能包含静态库,它会在运行时失败。

【讨论】:

谢谢,关于“1”解决方案中的第三个公告 - 我如何在没有链接库的情况下构建我的框架?如果我这样做,我的框架将无法构建(我正在构建动态框架),它将给出“没有这样的模块 GoogleMobileAds.framework”。我告诉消费者添加依赖项没有问题,但我如何构建我的框架?关于第二个解决方案,我的框架产品是“MyFramework.framework”文件夹,其中不包含 *.a 文件,所以我如何更改内部类,但 dylib 和 swiftmodule 类 如果你正在构建一个动态框架,你应该选择“2”。路线。只是不要导出您要导出的符号以外的任何符号。请参阅此处了解如何隐藏符号:developer.apple.com/library/mac/documentation/DeveloperTools/… 我使用 Swift 进行编程,据我所知,它没有“导出”或 #define 选项。我尝试在 xcode 中将“默认隐藏的符号”设置为 true,但似乎没有效果。此外,我要隐藏的符号是我导入到框架中的第三方库(不是我的任何书面代码)是否适用于这里?如何在 Swift 中正确地做到这一点?我为此苦苦挣扎了几个星期,任何帮助将不胜感激 我假设你的库是用 ObjC 编写的。您是如何将 Swift 类添加到静态库中的?我以为 Xcode 不支持。 查看更新后的答案,我添加了一个示例来帮助解释它。【参考方案3】:

您总是可以像这样使用框架中的类:

import Alamofire

let request = Alamofire.Request(...)

如果你自己的框架中有Request类,你可以用同样的方式使用它:

import YourFramework

let request = YourFramework.Request(...)

不会有任何冲突。

【讨论】:

试过了 - 不工作。在我的情况下,它在我的框架和消费者项目中具有相同的类的相同框架“GoogleMobileAds”【参考方案4】:

我之前也遇到过类似的问题,但我使用的是 Objective-C 第三方框架。通过使用桥接头将框架拖放到消费者项目中来解决问题,并使您的框架有点封装。这只是我的经验,因此可能不适用于您的情况,但不妨在这里分享,以防万一。

【讨论】:

如何在我的代码中不链接第三方框架的情况下构建我的框架(除非我链接它的二进制文件,否则它不会构建)

以上是关于如何解决 Cocoa touch 框架中的符号名称冲突的主要内容,如果未能解决你的问题,请参考以下文章

如何使 Cocoa Touch 框架在应用程序中保持最新

Cocoa Touch 框架无法在嵌入项目中的模拟器上调试

如何将用 Swift 编写的 Cocoa Touch 框架导入 .pch 文件?

《从零开始学Swift》学习笔记(Day 63)——Cocoa Touch设计模式及应用之单例模式

将 cocoapod 依赖项添加到 cocoa touch 框架

将 Cocoa Touch 纬度/经度转换为没有度数符号的字符串