[英]What is the best way to solve an Objective-C namespace collision?
Objective-C没有名称空间; 与C非常相似,所有内容都位于一个全局名称空间内。 常见的做法是在类的前面加上缩写,例如,如果您在IBM工作,则可以给它们加上“ IBM”作为前缀。 如果您为Microsoft工作,则可以使用“ MS”; 等等。 有时,缩写是指项目,例如Adium在类的前面加上“ AI”(因为后面没有公司可以使用缩写)。 苹果为类添加NS前缀,并说该前缀仅保留给Apple使用。
到目前为止一切顺利。 但是在类名前面附加2到4个字母是一个非常非常有限的名称空间。 例如,MS或AI可能具有完全不同的含义(例如AI可能是人工智能),并且其他一些开发人员可能决定使用它们并创建一个同名的类。 Bang ,名称空间冲突。
好的,如果这是您自己的类之一与正在使用的外部框架之间的冲突,则可以轻松更改类的命名,没什么大不了的。 但是,如果您使用两个外部框架,这两个框架都没有来源并且您不能更改,那该怎么办? 您的应用程序与它们两者都链接,您会遇到名称冲突。 您将如何解决这些问题? 仍然可以同时使用两个类的最佳方法是解决它们?
在C语言中,您可以通过不直接链接到库来解决这些问题,而是在运行时使用dlopen()加载库,然后使用dlsym()找到要查找的符号并将其分配给全局符号(可以以您喜欢的任何方式命名),然后通过此全局符号进行访问。 例如,如果由于某些C库具有名为open()的函数而发生冲突,则可以定义一个名为myOpen的变量,并将其指向该库的open()函数,因此当您要使用系统open()时,您只需要使用open(),当您要使用另一个时,可以通过myOpen标识符进行访问。
在Objective-C中是否可能有类似的事情,如果没有,是否可以使用其他聪明,棘手的解决方案来解决名称空间冲突? 有任何想法吗?
只是为了澄清这一点:当然欢迎提出建议如何避免名称空间冲突或如何创建更好的名称空间的答案; 但是,我不会接受它们作为答案,因为它们不能解决我的问题。 我有两个库,它们的类名冲突。 我不能改变他们。 我没有任何一个的来源。 碰撞已经存在,有关如何避免碰撞的提示将不再有用。 我可以将它们转发给这些框架的开发人员,并希望他们将来选择更好的名称空间,但是目前我正在寻找一种解决方案,以在单个应用程序中立即使用这些框架。 有什么解决办法可以做到这一点?
从根本上讲,用唯一前缀为类添加前缀是唯一的选择,但是有几种方法可以减轻这种麻烦和麻烦。 还有就是选择一个长时间的讨论在这里 。 我最喜欢的是@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指向的数据,则无需复制或重命名类。 您可以创建一个新类,然后复制ivars,方法,属性和类别。 还有更多工作要做,但有可能。 但是,框架中的其他类引用错误的类仍然会有问题。
编辑:据我所知,C和Objective-C运行时之间的根本区别在于,在加载库时,这些库中的函数包含指向它们引用的任何符号的指针,而在Objective-C中,它们包含字符串的表示形式。这些符号的名称。 因此,在您的示例中,可以使用dlsym获取内存中该符号的地址并将其附加到另一个符号上。 库中的其他代码仍然有效,因为您没有更改原始符号的地址。 Objective-C使用查找表将类名映射到地址,它是1-1映射,因此不能有两个具有相同名称的类。 因此,要加载这两个类,必须更改其中一个的名称。 但是,当其他类需要访问具有该名称的类之一时,它们将向查找表询问其地址,并且给定原始类的名称,查找表将永远不会返回重命名类的地址。
已经有几个人共享了一些棘手和巧妙的代码,这些代码可能有助于解决问题。 有些建议可能有用,但所有建议都不理想,有些建议实施起来很讨厌。 (有时不可避免地会出现丑陋的骇客,但我会尽一切可能避免它们。)从实际的角度来看,这是我的建议。
我猜想许可费用,条款和期限可能会阻止对这些问题立即采取行动。 希望您能够尽快解决冲突。 祝好运!
这很麻烦,但是您可以使用分布式对象 ,以便仅将这些类之一保留在从属程序地址中并对其进行RPC。 如果您来回传递大量的内容,那将变得很混乱(如果两个类都直接操作视图等,则可能无法实现)。
还有其他潜在的解决方案,但其中许多取决于实际情况。 特别是,您使用的是现代运行时间还是旧式运行时,是32位还是64位胖或单一体系结构,您要定位的操作系统版本,是动态链接,静态链接,还是可以选择?可以做一些可能需要维护新软件更新的事情。
如果您真的很绝望,您可以做的是:
上面的内容将非常耗费人力,并且如果您需要针对多个架构和不同的运行时版本实施它,那将是非常不愉快的,但是绝对可以使它工作。
您是否考虑过使用运行时函数(/usr/include/objc/runtime.h)将一个冲突的类克隆为一个非冲突类,然后加载冲突类框架? (这需要在不同的时间加载冲突的框架才能工作。)
您可以在运行时检查类ivars,方法(具有名称和实现地址)和名称,并动态创建自己的类以具有相同的ivar布局,方法名称/实现地址,并且仅在名称上有所不同(以避免碰撞)
绝望的局势要求采取绝望的措施。 您是否考虑过入侵其中一个库的目标代码(或库文件),将冲突符号更改为另一个名称-长度相同但拼写不同(但建议名称长度相同)? 天生讨厌。
尚不清楚您的代码是否直接调用具有相同名称但实现不同的两个函数,或者冲突是否是间接的(也不清楚是否有任何区别)。 但是,至少有外部机会可以重命名。 最小化拼写差异也可能是一个主意,这样,如果符号在表中按排序顺序排列,则重命名不会使内容乱序。 如果它们搜索的数组未按预期的排序顺序,则二进制搜索之类的操作会感到不高兴。
@compatibility_alias
将能够解决类名称空间冲突,例如
@compatibility_alias NewAliasClass OriginalClass;
但是, 这不会解决任何枚举,typedef或协议名称空间冲突 。 此外,它不能与原始类的@class
正向decl一起使用。 由于大多数框架都将带有这些非类的东西,例如typedef,所以您可能无法仅通过compatible_alias来解决命名空间问题。
我看过一个与您类似的问题 ,但是我可以访问源代码并正在构建框架。 我为此找到的最佳解决方案是有条件地将@compatibility_alias
与#defines配合使用以支持enums / @compatibility_alias
/ protocols / @compatibility_alias
您可以在有问题的标头的编译单元上有条件地执行此操作,以最大程度地减少在其他冲突框架中扩展内容的风险。
看来问题在于您无法在同一翻译单元(源文件)中引用来自两个系统的头文件。 如果在库周围创建Objective-C包装器(使它们在过程中更有用),并且仅在包装器类的实现中#include每个库的标头,则可以有效地分隔名称冲突。
我在Objective-C方面没有足够的经验(只是入门),但是我相信这就是我在C语言中要做的事情。
如果冲突仅在静态链接级别,则可以选择使用哪个库来解析符号:
cc foo.o -ldog bar.o -lcat
如果foo.o
和bar.o
两个参考符号rat
然后libdog
将解决foo.o
的rat
和libcat
将解决bar.o
的rat
。
只是一个想法..未经测试或证明,可能是标记的方式,但是您是否考虑过从更简单的框架..或至少是它们的接口编写用于类的适配器?
如果要围绕较简单的框架(或访问最少的接口)编写包装器,则不可能将该包装器编译为库。 鉴于该库是预编译的,并且只需要分发其标头,您将有效地隐藏基础框架,并且可以自由地将其与第二个框架进行冲突合并。
我当然理解有时可能需要同时使用两个框架中的类,但是,您可以为该框架的其他类适配器提供工厂。 基于这一点,我想您需要进行一些重构以从两个框架中提取出您正在使用的接口,这应该为您构建包装器提供一个不错的起点。
您可以在需要时使用该库,也可以在需要包装的库中的其他功能时进行构建,并在更改时仅重新编译即可。
再次,没有经过证明,但感觉就像是添加了一个视角。 希望能帮助到你 :)
给文件加上前缀是我所知道的最简单的解决方案。 Cocoadev有一个命名空间页面,这是社区为避免命名空间冲突而做出的努力。 随时将您自己的列表添加到该列表中,我相信这就是它的目的。
如果发生冲突,我建议您认真考虑如何从应用程序中重构其中一个框架。 发生冲突表明两者正在做着类似的事情,您可能只需重构应用程序就可以使用额外的框架。 这不仅可以解决您的名称空间问题,而且可以使您的代码更健壮,更易于维护和更高效。
在一个更具技术性的解决方案上,如果我处于您的位置,这将是我的选择。
如果您有两个具有相同功能名称的框架,则可以尝试动态加载框架。 这将是优雅的,但可能的。 我不知道如何使用Objective-C类。 我猜NSBundle
类将具有将加载特定类的方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.