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

What is the best way to solve an Objective-C namespace collision?

Objective-C没有名称空间;它与C非常类似,所有内容都在一个全局名称空间中。通常的做法是在类前面加上首字母,例如,如果您在IBM工作,则可以在类前面加上"ibm";如果您在Microsoft工作,则可以使用"ms";等等。有时,缩写词指的是项目,例如,adium在类前面加上"ai"(因为后面没有公司,所以你可以用缩写词)。Apple在类前加上ns,并表示这个前缀只为Apple保留。

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

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

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

在objective-c中是否有类似的可能?如果没有,是否还有其他聪明、棘手的解决方案可以用来解决名称空间冲突?有什么主意吗?

更新:

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


在类前面加上一个惟一的前缀从根本上说是唯一的选择,但是有几种方法可以减少这种麻烦和丑陋。这里有很多关于选择的讨论。我最喜欢的是@compatibility_aliasobjective-c编译器指令(在这里描述)。您可以使用@compatibility_alias来"重命名"类,允许您使用fqdn或某些此类前缀来命名类:

1
2
3
4
5
6
7
8
9
10
11
@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来修改接口构建器nib/xib中的类名,这样就不必编写完整的com-u文件了。在接口生成器中。

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


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

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

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

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


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

  • 在任何情况下,都要将冲突告知这两个框架的开发人员,并明确指出他们未能避免和/或处理冲突会给您带来真正的业务问题,如果无法解决,这可能会导致业务收入损失。强调在每个类的基础上解决现有冲突是一个不那么具有侵入性的修复,但要完全更改它们的前缀(或者如果它们当前不在,则使用一个前缀,这让它们感到羞愧!)是确保他们不会再看到相同问题的最佳方法。
  • 如果命名冲突仅限于一组相当小的类,那么请查看您是否可以只处理这些类,特别是如果您的代码没有直接或间接地使用其中一个冲突类。如果是这样,请查看供应商是否将提供不包含冲突类的框架的自定义版本。如果不是,坦率地说,他们的僵化正在减少您使用他们的框架的投资回报率。不要因为有理由而感到不快-客户总是对的。;-)
  • 如果一个框架更"可有可无",您可以考虑用第三方或自制的另一个框架(或代码组合)替换它。(后者是不受欢迎的最坏情况,因为它肯定会产生额外的业务成本,包括开发和维护。)如果您这样做,请将您决定不使用其框架的确切原因告知该框架的供应商。
  • 如果这两个框架都被认为是应用程序不可或缺的,那么可以探索将其中一个框架的使用考虑到一个或多个单独的进程中的方法,也许可以按照Louis Gerbarg的建议通过Do进行通信。根据交流的程度,这可能没有你想象的那么糟糕。一些程序(包括QuickTime,我相信)使用这种方法通过在Leopard中使用安全带沙盒配置文件来提供更精细的安全性,这样只允许代码的特定子集执行关键或敏感的操作。性能是一种权衡,但可能是你唯一的选择。
  • 我猜,许可费、条款和期限可能会妨碍对这些问题立即采取行动。希望你能尽快解决冲突。祝你好运!


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

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

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

  • 不直接链接到某个库
  • 实现在加载时更改名称的objc运行时例程的另一个版本(签出objc4项目,具体需要做什么取决于我上面提出的许多问题,但不管答案是什么,都应该是可能的)。
  • 使用类似mach_override的东西来注入新的实现
  • 使用普通方法加载新库,它将通过修补的链接器例程并更改类名。
  • 上面的内容将非常耗费人力,如果您需要针对多个arch和不同的运行时版本实现它,这将是非常不愉快的,但它肯定可以工作。


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

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


    绝望的情况需要绝望的措施。您是否考虑过对其中一个库的目标代码(或库文件)进行黑客攻击,将冲突符号更改为另一个名称-长度相同但拼写不同(但建议使用相同长度的名称)?天生讨厌。

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


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

    1
    @compatibility_alias NewAliasClass OriginalClass;

    但是,这不会解决任何枚举、typedef或协议命名空间冲突。此外,它还不能很好地与原始类的@class向前倾斜。由于大多数框架都会附带这些非类的东西,比如typedef,所以您可能无法仅使用兼容性u别名来解决名称空间问题。

    我研究了一个与您类似的问题,但是我可以访问源代码,并且正在构建框架。我找到的最好的解决方案是使用@compatibility_alias,有条件地使用带有定义的EDOCX1来支持枚举/typedefs/协议等。您可以有条件地在有关头的编译单元上执行此操作,以最大限度地降低在其他冲突框架中扩展内容的风险。


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

    在目标C中我没有足够的经验(刚刚开始),但我相信这就是我在目标C中要做的。


    只是个想法……未经测试或验证,可能是标记的方式,但在您是否考虑为您从更简单的框架中使用的类编写适配器中。或者至少是他们的界面?

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

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

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

    再一次,没有任何证明,但感觉像是在增加一个视角。希望有帮助:)


    如果碰撞仅在静态链接级别,则可以选择用于解析符号的库:

    1
    cc foo.o -ldog bar.o -lcat

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


    如果发生冲突,我建议您仔细考虑如何从应用程序中重构其中一个框架。发生冲突意味着这两者正在做与实际相似的事情,您可能只需重构应用程序就可以使用一个额外的框架。这不仅可以解决您的名称空间问题,而且可以使您的代码更加健壮、易于维护和高效。

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


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

    http://www.cocadev.com/index.pl?选择您的自定义


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