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

时间:2008-10-07 13:27:41

标签: objective-c cocoa macos namespaces

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中是否有类似的可能性,如果没有,是否还有其他聪明,棘手的解决方案可以使用解析命名空间冲突?有什么想法吗?


更新

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

13 个答案:

答案 0 :(得分:93)

使用唯一前缀为您的类添加前缀基本上是唯一的选择,但有几种方法可以减少繁琐和丑陋。关于选项here的讨论很长。我最喜欢的是@compatibility_alias Objective-C编译器指令(描述为here)。您可以使用@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)一起使用。

答案 1 :(得分:47)

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

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

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

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

答案 2 :(得分:12)

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

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

答案 3 :(得分:8)

这很糟糕,但你可以使用distributed objects来保持其中一个类只在一个从属程序地址和RPC中。如果你来回传递大量的东西(如果两个类都直接操纵视图等可能不可能),那将会变得混乱。

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

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

  1. 不直接链接其中一个图书馆
  2. 实现在加载时更改名称的objc运行时例程的备用版本(签出objc4项目,您需要做什么取决于我上面提到的一些问题,但它应该是无论答案是什么都可能。)
  3. 使用mach_override之类的内容注入新的实现
  4. 使用常规方法加载新库,它将通过修补的链接器例程并更改其className
  5. 以上将是相当费力的工作,如果你需要针对多个arch和不同的运行时版本实现它,那将是非常不愉快的,但它绝对可以使用。

答案 4 :(得分:4)

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

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

答案 5 :(得分:3)

绝望的情况需要绝望的措施。您是否考虑过攻击其中一个库的目标代码(或库文件),将碰撞符号更改为替代名称 - 长度相同但拼写不同(但推荐名称长度相同)?天生就讨厌。

目前尚不清楚你的代码是直接调用具有相同名称但实现不同的两个函数,还是冲突是间接的(也不清楚它是否有任何区别)。但是,至少有一个外部机会,重命名将起作用。最小化拼写中的差异也可能是一个想法,因此如果符号在表中按排序顺序排列,则重命名不会使事情无序。如果他们搜索的数组没有按照预期的排序顺序,那么像二进制搜索这样的东西就会被惹恼。

答案 6 :(得分:1)

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

我在objective-c(刚入门)中没有足够的经验,但我相信这就是我在C中所做的。

答案 7 :(得分:1)

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

@compatibility_alias NewAliasClass OriginalClass;

但是,这将无法解析任何枚举,typedef或协议命名空间冲突。此外,它与原始类的@class前向decls不匹配。由于大多数框架都会附带像typedef这样的非类似的东西,因此您可能无法仅使用compatibility_alias来修复命名空间问题。

我查看了a similar problem to yours,但我可以访问源代码并构建框架。 我找到的最佳解决方案是使用@compatibility_alias有条件地使用#defines来支持enums / typedefs / protocols / etc.您可以在有问题的头文件的编译单元上有条件地执行此操作,以最大限度地降低在另一个冲突框架中扩展内容的风险。

答案 8 :(得分:0)

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

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

答案 9 :(得分:0)

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

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

答案 10 :(得分:0)

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

cc foo.o -ldog bar.o -lcat

如果foo.obar.o都引用了符号rat,则libdog将解析foo.o的{​​{1}}和rat将解决libcat的{​​{1}}。

答案 11 :(得分:0)

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

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

我当然感谢您可能有时需要同时使用两个框架中的类,但是,您可以为该框架的其他类适配器提供工厂。在这一点的背面,我猜你需要一些重构来从两个框架中提取你正在使用的接口,这应该为你构建你的包装器提供了一个很好的起点。

您可以在构建库时以及何时需要包装库中的其他功能,并在更改时重新编译。

再一次,没有证明,但感觉就像添加一个观点。希望它有所帮助:)

答案 12 :(得分:-1)

如果您有两个具有相同功能名称的框架,则可以尝试动态加载框架。这将是不优雅的,但可能。如何使用Objective-C类,我不知道。我猜测NSBundle类将有加载特定类的方法。