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


174

Objective-C没有名称空间;与C非常相似,所有内容都位于一个全局名称空间内。常见的做法是在类的前面加上缩写,例如,如果您在IBM工作,则可以给它们加上“ IBM”作为前缀。如果您为Microsoft工作,则可以使用“ MS”;等等。有时,缩写是指项目,例如Adium在类的前面加上“ AI”(因为后面没有公司可以使用缩写)。苹果为类添加NS前缀,并说该前缀仅保留给苹果。

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

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

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

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


更新:

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


7
您有一个很好的问题(如果您需要两个名称冲突的框架该怎么办),但是它被埋在了文本中。进行修改以使其更加清晰,您将避免像现在这样简单的答案。
benzado

4
这是我对Objective-C语言当前设计的最大抱怨。查看下面的答案;那些真正解决了这个问题(NSBundle卸载,使用DO等)的都是骇人听闻的骇客,对于避免琐碎的名称空间冲突这样的琐碎事情来说,它们是不必要的。
erikprice

@erikprice:阿们 我正在学习obj-c,并遇到了这个问题。来到这里寻找简单的解决方案。
Dave Mateer'2

1
作为记录,从技术上讲,C和Objective-C都提供对多个名称空间的支持-尽管不完全是OP所需要的。见objectivistc.tumblr.com/post/3340816080/...

嗯,我不知道。那种糟糕的设计决定不是吗?
Nico 2012年

Answers:


47

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

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

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

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


5
我相信直到10.5或更高版本才支持捆绑卸载
奎因·泰勒,

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在Via Builder阶段使用它来修改Interface Builder nib / xib中的类名,从而不必在Interface Builder中编写完整的COM_WHATEVER_...。

最后的警告:因为这是一个编译器指令(也是一个不起眼的指令),所以它可能无法在编译器之间移植。特别是,我不知道它是否可以与LLVM项目中的Clang前端一起使用,尽管它应与LLVM-GCC(使用GCC前端的LLVM)一起使用。


4
支持兼容性_别名,我对此一无所知!谢谢。但这不能解决我的问题。我没有控制权更改我正在使用的任何一个框架的任何前缀,我只有它们采用二进制形式,并且它们会发生冲突。我该怎么办?
Mecki

@compatibility_alias行应放在哪个文件(.h或.m)中?
亚历克斯·巴森

1
@Alex_Basson @compatibility_alias可能应该放在标题中,以便在可见@interface声明的任何位置都可见。
巴里·沃克

我想知道您是否可以将@compatibility_alias与协议名称,typedef定义或其他相关内容一起使用?
阿里

好想法在这里。可以使用方法转换来防止NSClassFromString(ClassName)为别名类返回nil吗?可能很难找到所有带有类名的方法。
迪基·辛格

12

一些人已经共享了一些棘手和巧妙的代码,这些代码可能有助于解决问题。有些建议可能有用,但所有建议都不理想,有些建议实施起来很讨厌。(有时不可避免地会出现丑陋的骇客,但我会尽一切可能避免它们。)从实际的角度来看,这是我的建议。

  1. 无论如何,都应将冲突的两个框架告知开发人员,并明确指出他们未能避免和/或处理冲突会导致您遇到实际的业务问题,如果无法解决,则可能转化为业务收入损失。强调在解决每个类的现有冲突的同时减少了侵入性,但完全更改其前缀(或者如果不是当前前缀则使用一个前缀,并对其感到羞耻!)是确保它们不会出现的最佳方法。再次看到相同的问题。
  2. 如果命名冲突仅限于一组较小的类,请查看您是否可以仅解决这些类,特别是如果代码中没有直接或间接使用一个冲突的类。如果是这样,请查看供应商是否将提供不包含冲突类的自定义版本的框架。如果不是这样,请坦率地说他们的灵活性不足,从而降低了使用他们的框架的投资回报率。不要因为理性而大胆出击-客户永远是对的。;-)
  3. 如果一个框架更“可有可无”,则您可以考虑将其替换为第三方或自制软件的另一个框架(或代码组合)。(后一种情况是不希望出现的最坏情况,因为这肯定会导致开发和维护方面的额外业务成本。)如果这样做,请告知该框架供应商确切的决定为什么不使用他们的框架。
  4. 如果认为这两个框架对于您的应用程序同样不可或缺,请探索将其中一个框架的使用排除在一个或多个单独过程之外的方法,也许可以按照Louis Gerbarg的建议通过DO进行通信。根据沟通的程度,这可能没有您预期的那么糟糕。多个程序(我相信包括QuickTime)都使用此方法来提供通过使用Leopard中的Seatbelt沙箱配置文件提供的更精细的安全性,从而仅允许代码的特定子集执行关键或敏感操作。性能将是一个折衷,但可能是您唯一的选择

我猜想许可费,条款和期限可能会阻止对这些方面的任何立即采取行动。希望您能够尽快解决冲突。祝好运!


8

这很麻烦,但是您可以使用分布式对象,以便仅将这些类之一保留在从属程序地址中并对其进行RPC。如果您来回传递大量的东西,那将变得很混乱(如果两个类都直接操纵视图等,则可能无法实现)。

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

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

  1. 不直接链接到其中一个库
  2. 实现objc运行时例程的备用版本,该版本在加载时更改名称(签出objc4项目,您确切需要执行的操作取决于我在上面询问的许多问题,但是无论答案是什么,都应该是可能的)。
  3. 使用mach_override之类的东西注入您的新实现
  4. 使用常规方法加载新库,它将通过修补的链接器例程并更改其className

上面的内容将非常耗费人力,并且如果您需要针对多个架构和不同的运行时版本来实现它,那将是非常不愉快的,但是绝对可以使它工作。


4

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

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


3

绝望的局势要求采取绝望的措施。您是否考虑过入侵其中一个库的目标代码(或库文件),将冲突符号更改为另一个名称-长度相同但拼写不同(但建议名称长度相同)?天生讨厌。

尚不清楚您的代码是否直接调用具有相同名称但实现不同的两个函数,或者冲突是否是间接的(也不清楚是否有任何区别)。但是,至少有外部机会可以重命名。最小化拼写差异也可能是一个主意,这样,如果符号在表中按排序顺序排列,则重命名不会使内容乱序。如果它们搜索的数组未按预期的排序顺序,则二进制搜索之类的操作会感到不高兴。


更改磁盘上的库毫无疑问,因为许可证不允许这样做。可以更改内存中的符号,但是我看不到如何做到这一点(将lib加载到内存中,对其进行修改,然后将其传递给动态链接器...对此则没有任何方法)。
梅基

3
好的-是时候确定这两个库中哪个较不重要,并寻找替代品了。并向您付款的那一方解释,但您放弃更改的原因是由于这种冲突,他们可以通过解决问题来保留您的业务。
乔纳森·莱夫勒

2

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

@compatibility_alias NewAliasClass OriginalClass;

但是,这不会解决任何枚举,typedef或协议名称空间冲突。此外,它与@class原始类的正向表达式。由于大多数框架都将附带这些非类的东西,例如typedef,因此您可能无法仅通过compatible_alias来解决命名空间问题。

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


1

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

我在Objective-C方面没有足够的经验(只是入门),但是我相信这就是我在C语言中要做的事情。


1
但是,如果您尝试将两个包装程序头文件都包含在同一个文件中,您是否还会遇到冲突(因为它们每个人都需要自己包含各自框架的头文件)?
Wilco

0

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

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


如果文件中定义的对象引起了问题,则给文件加上前缀将有何帮助?我当然可以操纵h文件,但是当链接程序喜欢该框架时,链接程序将再也找不到对象了:-/
Mecki,

我相信,这将是最好的选择,而不是最初问题的解决方案。
阿里

这根本解决不了这个问题
Madbreaks 2013年

0

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

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


0

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

cc foo.o -ldog bar.o -lcat

如果 foo.obar.o两个参考符号rat,然后libdog将解决foo.orat,并libcat会解决bar.orat


0

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

如果要围绕较简单的框架(或访问最少的接口)编写包装器,则不可能将该包装器编译为库。由于库是预编译的,只有需要分发标头,您将有效地隐藏基础框架,并且可以自由地将其与第二个框架进行冲突合并。

我当然理解有时可能需要同时使用两个框架中的类,但是,您可以为该框架的其他类适配器提供工厂。基于这一点,我想您需要进行一些重构以从两个框架中提取出您正在使用的接口,这应该为您构建包装器提供一个不错的起点。

您可以在需要时使用该库,也可以在需要包装的库的其他功能时进行构建,并且在更改时只需重新编译即可。

再次,没有经过证明,但感觉像是添加了一个视角。希望能帮助到你 :)


-1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.