解决 Objective-C 名称空间冲突的最佳方法是什么?

Objective-C 没有名称空间; 它很像 C,所有东西都在一个全局名称空间内。通常的做法是在类前面加上首字母,例如,如果你在 IBM 工作,你可以在它们前面加上“ IBM”; 如果你在微软工作,你可以使用“ MS”; 等等。有时候项目的首字母是指项目,例如 Adium 类的前缀是“ AI”(因为项目背后没有公司可以使用首字母)。Apple 在类的前缀中加上 NS,并说这个前缀只为 Apple 保留。

目前为止一切顺利。但是将2到4个字母附加到前面的类名称是一个非常非常有限的名称空间。例如,MS 或 AI 可能有完全不同的含义(例如,AI 可能是人工智能) ,其他一些开发人员可能会决定使用它们,并创建一个同样命名的类。名称空间冲突。

好的,如果这是您自己的类和您正在使用的外部框架之间的冲突,您可以轻松地更改您的类的命名,没有什么大不了的。你的应用程序链接到他们两个,你会得到名称冲突。你打算怎么解决这些问题?什么是最好的方法来解决这些问题,使您仍然可以同时使用这两个类?

在 C 语言中,你可以通过不直接链接到库来解决这些问题,而是在运行时使用 dlopen ()加载库,然后使用 dlsym ()找到你正在寻找的符号,并将它分配给一个全局符号(你可以用任何方式命名它) ,然后通过这个全局符号访问它。例如,如果你有一个冲突,因为一些 C 库有一个名为 open ()的函数,你可以定义一个名为 myOpen 的变量,让它指向库的 open ()函数,因此当你想使用系统 open ()时,你只需要使用 open () ,当你想使用另一个函数时,你可以通过 myOpen 标识符来访问它。

Objective-C 中是否存在类似的可能性? 如果没有,是否有其他聪明、棘手的解决方案可以用来解决名称空间冲突?有什么想法吗?


更新:

只是为了澄清这一点: 建议如何避免名称空间冲突或如何创建更好的名称空间的答案当然是受欢迎的; 但是,我不会接受它们作为 答案,因为它们不能解决我的问题。我有两个库,它们的类名发生冲突。我不能改变它们,我没有任何一个的来源。撞击已经发生了,而提前知道如何避免的提示也不再有用。我可以将它们转发给这些框架的开发人员,并希望他们将来选择一个更好的名称空间,但目前我正在寻找一个解决方案,以便立即在单个应用程序中使用这些框架。有什么解决办法吗?

42488 次浏览

在文件前面加上前缀是我所知道的最简单的解决方案。 Cocoadev 有一个名称空间页面,这是一个避免名称空间冲突的社区努力。 请随意添加您自己的这个名单,我相信这就是它的作用。

Http://www.cocoadev.com/index.pl ? 选择你自己的前缀

在类前面加上一个唯一的前缀从根本上来说是唯一的选择,但是有几种方法可以减少这种繁琐和难看的程度。关于选项 给你有很长的讨论。我最喜欢的是 @compatibility_alias Objective-C 编译器指令(描述为 给你)。您可以使用 @compatibility_alias来“重命名”一个类,允许您使用 FQDN 或类似的前缀来命名您的类:

@interface COM_WHATEVER_ClassName : NSObject
@end


@compatibility_alias ClassName COM_WHATEVER_ClassName
// now ClassName is an alias for COM_WHATEVER_ClassName


@implementation ClassName //OK
//blah
@end


ClassName *myClass; //OK

作为完整策略的一部分,您可以在所有类的前缀中添加唯一的前缀,比如 FQDN,然后创建一个包含所有 @compatibility_alias的头文件(我想您可以自动生成该头文件)。

像这样的前缀的缺点是,除了编译器之外,在任何需要字符串中的类名的地方,都必须输入真正的类名(例如上面的 COM_WHATEVER_ClassName)。值得注意的是,@compatibility_alias是一个编译器指令,而不是一个运行时函数,因此 NSClassFromString(ClassName)将失败(返回 nil)——您必须使用 NSClassFromString(COM_WHATERVER_ClassName)。你可以通过构建阶段使用 ibtool来修改 Interface Builder nib/xib 中的类名,这样你就不必在 Interface Builder 中编写完整的 COM _ WHATEVER _... 。

最后的警告: 因为这是一个编译器指令(而且是一个模糊的指令) ,它可能不能跨编译器移植。特别是,我不知道它是否可以与 LLVM 项目的 Clang 前端一起工作,尽管它应该可以与 LLVM-GCC (使用 GCC 前端的 LLVM)一起工作。

如果你不需要同时使用这两个框架中的类,并且你的目标平台支持 NSBundle 卸载(OS X 10.4或更高版本,不支持 GNUStep) ,并且性能对你来说真的不是问题,我相信你可以在每次需要使用一个框架的时候加载它,然后卸载它并在需要使用另一个框架的时候加载另一个框架。

我最初的想法是使用 NSBundle 加载其中一个框架,然后复制或重命名该框架中的类,然后加载另一个框架。这里有两个问题。首先,我找不到一个函数来复制指向重命名的数据或者复制一个类,而在第一个框架中引用重命名类的任何其他类现在都会从另一个框架引用这个类。

如果有一种方法可以复制 IMP 指向的数据,那么就不需要复制或重命名类。您可以创建一个新类,然后复制变量、方法、属性和类别。还有很多工作要做,但还是有可能的。但是,框架中引用错误类的其他类仍然存在问题。

编辑: 根据我的理解,C 和 Objective-C 运行时之间的根本区别在于,当加载库时,这些库中的函数包含指向它们引用的任何符号的指针,而在 Objective-C 中,它们包含这些符号名称的字符串表示。因此,在您的示例中,可以使用 dlsym 获取内存中的符号地址并将其附加到另一个符号。库中的其他代码仍然有效,因为您没有更改原始符号的地址。Objective-C 使用一个查找表将类名映射到地址,这是一个1-1映射,因此不能有两个具有相同名称的类。因此,要加载这两个类,其中一个类的名称必须更改。但是,当其他类需要访问具有该名称的类时,它们会向查找表询问其地址,而查找表将永远不会返回给定原始类名的重命名类的地址。

您是否考虑过使用运行时函数(/usr/include/objecc/runtime.h)将一个冲突类克隆到一个非冲突类中,然后加载冲突类框架?(这需要在不同的时间加载碰撞框架才能工作。)

您可以在运行时检查类 ivar、方法(带有名称和实现地址)和名称,并动态创建自己的 ivar 布局、方法名称/实现地址,只有名称不同(以避免冲突)

非常情况需要非常手段。你是否考虑过破解其中一个库的目标代码(或库文件) ,将碰撞符号更改为另一个名称——相同长度但拼写不同(但推荐使用相同长度的名称) ?本来就很恶心。

不清楚您的代码是否直接调用具有相同名称但不同实现的两个函数,或者冲突是否是间接的(也不清楚它是否会产生任何差异)。然而,重命名至少还有一个极小的可能性。这可能也是一个想法,以最小化拼写的差异,以便如果符号是在一个表中的排序顺序,重命名不会移动顺序的东西。如果二进制搜索所搜索的数组没有按照预期的顺序排列,那么二进制搜索就会失败。

这很恶心,但是您可以使用 分布式对象,以便将其中一个类仅保存在一个从属程序地址和 RPC 中。如果您来回传递大量的内容,这将会变得很混乱(如果两个类都直接操作视图等,这可能是不可能的)。

还有其他可能的解决方案,但其中很多取决于确切的情况。特别是,你是使用现代的还是传统的运行时,你是胖的还是单一的架构,32位还是64位,你的目标操作系统是什么,你是动态链接,静态链接,还是你有选择,做一些可能需要维护新软件更新的事情是否可行。

如果你真的绝望了,你可以做的是:

  1. 不直接链接到其中一个库
  2. 实现一个在加载时更改名称的 obc 运行时例程的替代版本(检查 物件4项目,您确切需要做什么取决于我上面问的一些问题,但是不管答案是什么都应该是可能的)。
  3. 使用类似 马赫 _ 覆盖的东西来注入新的实现
  4. 使用普通方法加载新库,它将通过修补后的链接器例程并更改其 className

以上将是相当劳动密集型的,如果你需要实现它对多个拱门和不同的运行时版本,这将是非常不愉快的,但它肯定可以使工作。

如果遇到冲突,我建议您认真考虑一下如何在应用程序中重构一个框架。发生冲突意味着两者正在做类似的事情,您可以通过重构应用程序来避免使用额外的框架。这不仅可以解决名称空间问题,而且可以使代码更健壮、更易于维护和更高效。

更技术性的解决方案,如果我在你的位置,这将是我的选择。

问题似乎在于,您不能在同一个翻译单元(源文件)中引用来自两个系统的头文件。如果您在库周围创建 Objective-c 包装器(使它们在过程中更加可用) ,并且在包装器类的实现中只包含每个库的标头,那将有效地隔离名称冲突。

我没有足够的经验与这个目标-c (刚刚开始) ,但我相信这是我会做的 C。

一些人已经分享了一些复杂而聪明的代码,这些代码可能有助于解决这个问题。有些建议可能有效,但是所有的建议都不够理想,而且有些建议实施起来非常糟糕。(有时丑陋的黑客行为是不可避免的,但我会尽可能避免。)从实际的角度来看,这里是我的建议。

  1. 在任何情况下,告知 都有框架的开发人员这种冲突,并明确表示他们未能避免和/或处理这种冲突会给您带来真正的业务问题,如果不解决这些问题,可能会导致业务收入的损失。强调在每个类的基础上解决现有的冲突是一个较少干扰的修复方法,完全改变它们的前缀(或者使用一个前缀,如果它们不是当前的,并为它们感到羞耻!)是确保他们不会再看到同样问题的最好方法。
  2. 如果命名冲突仅限于一组相当小的类,那么看看是否可以只处理这些类,尤其是当代码没有直接或间接地使用其中一个冲突类时。如果是这样,看看供应商是否会提供一个不包含冲突类的框架自定义版本。如果没有,坦率地说,他们的不灵活性正在减少您使用他们的框架的 ROI。不要为在合理范围内强求而感到难过ーー顾客永远是对的。;-)
  3. 如果一个框架更“可有可无”,您可以考虑用另一个框架(或代码组合)来替换它,无论是第三方的还是自制的。(后者是最糟糕的情况,因为它肯定会带来额外的业务成本,包括开发和维护成本。)如果你这样做了,告诉供应商你为什么决定不使用他们的框架。
  4. 如果这两个框架被认为对您的应用程序同样不可或缺,那么探索将其中一个框架的使用分解为一个或多个独立进程的方法,也许可以像 Louis Gerbarg 建议的那样,通过 DO 进行通信。取决于沟通的程度,这可能没有你想象的那么糟糕。有几个程序(我相信包括 QuickTime)使用这种方法提供更细粒度的安全性,使用 豹式安全带沙箱配置文件只允许代码的一个特定子集执行关键或敏感的操作。表现将是一种权衡,但可能是你唯一的选择

我猜测许可费、期限和持续时间可能会阻止对这些问题采取即时行动。希望你们能尽快解决冲突。祝你好运!

如果冲突只是在静态链接级别,那么您可以选择使用哪个库来解析符号:

cc foo.o -ldog bar.o -lcat

如果 foo.obar.o都引用符号 rat,那么 libdog将解析 foo.orat,而 libcat将解析 bar.orat

如果有两个具有相同函数名的框架,可以尝试动态加载这些框架。虽然不太雅观,但也是有可能的。如何使用 Objective-C 类,我不知道。我猜想 NSBundle类将有加载特定类的方法。

只是个想法。.没有经过测试或证明,可能是标志性的方法,但是您是否考虑过从更简单的框架中为类的使用编写一个适配器。.或者至少是他们的接口?

如果要围绕较简单的框架(或者访问最少的接口)编写一个包装器,那么将该包装器编译到库中是不可能的。如果库是预编译的,并且只需要分发 它的头文件,那么您就可以有效地隐藏底层框架,并且可以随意地将其与第二个框架组合起来。

当然,有时候您可能需要同时使用来自两个框架的类,但是,您可以为该框架的更多类适配器提供工厂。在这一点的后面,我猜想您需要一些重构来从两个框架中提取出您正在使用的接口,这将为您构建包装器提供一个很好的起点。

当您需要从包装的库中获得更多功能时,您可以构建在库之上,当它发生变化时,只需重新编译。

再说一次,虽然没有被证实,但是感觉像是增加了一个视角。希望能有所帮助:)

@compatibility_alias将能够解决类名称空间冲突,例如。

@compatibility_alias NewAliasClass OriginalClass;

然而,这不会解决任何枚举、类型定义或协议命名空间冲突。此外,它不能发挥良好的 @class前进分贝的原始类。由于大多数框架都带有这些非类的东西,比如 typedef,所以很可能不能仅仅通过兼容性 _ 别名来修复名称空间问题。

我查看了 一个和你类似的问题,但是我可以访问源代码,并且正在构建框架。 我发现的最佳解决方案是有条件地使用 @compatibility_alias,并使用 # Definition 来支持枚举/typedefs/协议等。您可以有条件地在相关头的编译单元上执行此操作,以最小化在其他碰撞框架中扩展内容的风险。