176

Objective-C 没有命名空间;它很像 C,一切都在一个全局命名空间中。通常的做法是在类前面加上首字母,例如,如果你在 IBM 工作,你可以在它们前面加上“IBM”;如果你为微软工作,你可以使用“MS”;等等。有时缩写是指项目,例如 Adium 为类加上“AI”前缀(因为它背后没有公司,你可以使用缩写)。Apple 用 NS 为类添加前缀,并表示此前缀仅供 Apple 使用。

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

好的,如果这是您自己的一个类和您正在使用的外部框架之一之间的冲突,您可以轻松更改类的命名,没什么大不了的。但是,如果您使用两个外部框架,这两个框架您都没有源代码并且无法更改,该怎么办?您的应用程序与它们都链接,并且您会遇到名称冲突。你将如何解决这些问题?以您仍然可以使用这两个类的方式解决它们的最佳方法是什么?

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

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


更新:

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

4

13 回答 13

93

使用唯一前缀为类添加前缀基本上是唯一的选择,但有几种方法可以减少繁琐和丑陋。这里对选项进行了长时间的讨论。我最喜欢的是@compatibility_aliasObjective-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(LLVM 使用 GCC 前端)。

于 2008-10-07T22:17:27.113 回答
48

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

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

如果有办法复制 IMP 指向的数据,则不需要复制或重命名类。您可以创建一个新类,然后复制 ivars、方法、属性和类别。更多的工作,但这是可能的。但是,您仍然会遇到框架中引用错误类的其他类的问题。

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

于 2008-10-08T04:51:59.030 回答
12

一些人已经分享了一些可能有助于解决问题的棘手而聪明的代码。一些建议可能有效,但都不太理想,其中一些实施起来非常糟糕。(有时丑陋的黑客是不可避免的,但我会尽可能避免它们。)从实际的角度来看,这是我的建议。

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

我猜许可费用、条款和期限可能会阻止对这些点的即时行动。希望您能够尽快解决冲突。祝你好运!

于 2009-06-16T17:43:34.433 回答
8

这很糟糕,但是您可以使用分布式对象来将其中一个类仅保留在从属程序地址和 RPC 中。如果你来回传递大量的东西,那将会变得一团糟(如果两个类都直接操作视图等,这可能是不可能的)。

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

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

  1. 不直接链接到其中一个库
  2. 实现 objc 运行时例程的替代版本,在加载时更改名称(检查objc4项目,您究竟需要做什么取决于我上面提出的一些问题,但无论答案是什么,它都应该是可能的)。
  3. 使用类似mach_override的东西来注入你的新实现
  4. 使用普通方法加载新库,它将通过修补的链接器例程并更改其类名

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

于 2009-02-28T00:00:52.937 回答
4

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

您可以使用运行时检查类 ivars、方法(具有名称和实现地址)和名称,并动态创建您自己的以具有相同的 ivar 布局、方法名称/实现地址,并且仅在名称上有所不同(以避免碰撞)

于 2008-12-07T18:35:53.323 回答
3

绝望的情况需要采取绝望的措施。您是否考虑过破解其中一个库的目标代码(或库文件),将冲突符号更改为另一个名称 - 长度相同但拼写不同(但建议,名称长度相同)?天生恶心。

不清楚您的代码是否直接调用具有相同名称但实现不同的两个函数,或者冲突是否是间接的(也不清楚它是否有任何区别)。但是,重命名至少有一个外部机会。将拼写差异最小化也可能是一个想法,这样如果符号在表中按排序顺序排列,重命名就不会使事情乱序。如果他们正在搜索的数组未按预期排序,则二进制搜索之类的事情会变得不安。

于 2009-02-27T07:49:06.813 回答
2

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

@compatibility_alias NewAliasClass OriginalClass;

但是,这不会解决任何枚举、类型定义或协议命名空间冲突。此外,它不能很好地与@class原始类的前向 decls 配合使用。由于大多数框架都会附带这些非类的东西,如 typedef,您可能无法仅使用 compatible_alias 来解决命名空间问题。

我查看了与您类似的问题,但我可以访问源代码并正在构建框架。我为此找到的最佳解决方案是@compatibility_alias有条件地使用 #defines 来支持 enums/typedefs/protocols/etc。您可以有条件地在相关标头的编译单元上执行此操作,以最大程度地减少在其他冲突框架中扩展内容的风险。

于 2013-12-26T19:04:26.123 回答
1

似乎问题在于您无法在同一翻译单元(源文件)中引用来自两个系统的头文件。如果您在库周围创建objective-c 包装器(使它们在过程中更有用),并且在包装器类的实现中仅#include 每个库的标头,这将有效地分离名称冲突。

我在 Objective-C 中没有足够的经验(刚刚开始),但我相信这就是我在 C 中会做的事情。

于 2009-03-04T03:54:42.310 回答
0

为文件添加前缀是我所知道的最简单的解决方案。Cocoadev 有一个命名空间页面,这是社区为避免命名空间冲突所做的努力。随意将您自己的添加到此列表中,我相信这就是它的用途。

http://www.cocoadev.com/index.pl?ChooseYourOwnPrefix

于 2008-10-07T14:14:04.560 回答
0

如果您遇到冲突,我建议您认真考虑如何从您的应用程序中重构其中一个框架。发生冲突表明两者正在做类似的事情,您可能只需通过重构您的应用程序就可以使用额外的框架。这不仅可以解决您的命名空间问题,还可以使您的代码更健壮、更易于维护和更高效。

对于更技术性的解决方案,如果我处于您的位置,这将是我的选择。

于 2009-03-01T17:18:50.127 回答
0

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

cc foo.o -ldog bar.o -lcat

如果foo.obar.o都引用该符号rat,则将libdog解析foo.o'srat并将libcat解析bar.o's rat

于 2012-05-20T03:27:45.863 回答
0

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

如果您要围绕较简单的框架(或您访问最少的接口)编写一个包装器,则不可能将该包装器编译到库中。鉴于该库是预编译的,并且只需要分发它的头文件,您将有效地隐藏底层框架,并且可以自由地将它与第二个框架结合起来并发生冲突。

当然,我很欣赏有时您可能需要同时使用两个框架中的类,但是,您可以为该框架的进一步类适配器提供工厂。在这一点的背后,我想您需要进行一些重构才能从两个框架中提取您正在使用的接口,这应该为您构建包装器提供一个很好的起点。

当您需要来自包装库的更多功能时,您可以在库的基础上进行构建,并在它发生更改时简单地重新编译。

同样,没有得到证实,但感觉就像添加了一个视角。希望能帮助到你 :)

于 2013-05-31T16:01:52.787 回答
-1

如果您有两个具有相同函数名称的框架,您可以尝试动态加载框架。这将是不雅的,但可能。我不知道如何使用 Objective-C 类来做到这一点。我猜NSBundle该类将具有加载特定类的方法。

于 2013-01-18T00:54:49.267 回答