什么是解决Objective-C命名空间碰撞的最好方法?
Objective-C没有名称空间; 这很像C,一切都在一个全局的命名空间。 通常的做法是用类名前缀加前缀,例如,如果你在IBM工作,你可以用“IBM”作为前缀。 如果你在微软工作,你可以使用“MS”; 等等。 有时候,首字母缩写是指项目,例如Adium在“AI”前加上类(因为没有任何公司可以采用这个缩写)。 苹果公司前缀与NS的类,并说这个前缀是只保留给苹果。
到目前为止这么好。 但是将2到4个字母附加到前面的类名是一个非常非常有限的名称空间。 例如,MS或AI可能具有完全不同的含义(例如,AI可能是人工智能),而其他开发人员可能会决定使用它们并创build一个同名的类。 砰 ,命名空间相撞。
好吧,如果这是你自己的类和你正在使用的外部框架之间的碰撞,你可以很容易地改变你的类的命名,没有什么大不了的。 但是,如果使用两个外部框架,那么这两个框架都没有源代码,而且不能更改? 您的应用程序与他们两个链接,你会得到名称冲突。 你将如何去解决这些问题? 什么是最好的方式来解决他们的方式,你仍然可以使用这两个类?
在C中,可以通过不直接链接到库来解决这些问题,而是使用dlopen()在运行时加载库,然后使用dlsym()查找要查找的符号并将其分配给全局符号可以用你喜欢的任何方式命名),然后通过这个全局符号来访问它。 例如,如果因为某个C库有一个名为open()的函数而发生冲突,则可以定义一个名为myOpen的variables,并指向该库的open()函数,因此,如果要使用系统open() ,你只需要使用open(),当你想使用另一个时,你可以通过myOpen标识符来访问它。
在Objective-C中有类似的可能性,如果不是的话,还有没有其他聪明的,棘手的解决scheme,你可以使用解决命名空间冲突? 有任何想法吗?
更新:
为了澄清这个问题:如何避免命名空间冲突或如何创build一个更好的命名空间的答案当然是受欢迎的; 然而,我不会接受他们作为答案,因为他们不能解决我的问题。 我有两个图书馆,他们的class名相撞。 我不能改变他们; 我没有任何一个的来源。 碰撞已经在那里,如何提前避免的提示将无济于事。 我可以将它们转发给这些框架的开发人员,并希望他们将来能够select一个更好的名称空间,但是目前我正在寻找一种解决scheme,在单个应用程序中使用框架。 任何解决scheme,使这成为可能?
如果您不需要同时使用两个框架中的类,并且您的目标平台支持NSBundle卸载(OS X 10.4或更高版本,不支持GNUStep),并且性能对您来说确实不是问题,我相信您可以在每次需要使用其中一个类时加载一个框架,然后在需要使用另一个框架时将其卸载并加载另一个框架。
我最初的想法是使用NSBundle加载其中一个框架,然后复制或重命名该框架内的类,然后加载其他框架。 这有两个问题。 首先,我找不到一个函数来复制指向重命名或复制类的数据,而引用重命名类的第一个框架中的任何其他类现在将引用另一个框架中的类。
如果有方法复制IMP指向的数据,则不需要复制或重命名类。 你可以创build一个新的类,然后复制ivars,方法,属性和类别。 更多的工作,但这是可能的。 但是,在引用错误的类的框架中,其他类仍然存在问题。
编辑:C和Objective-C运行时间之间的根本区别是,据我所知,当加载库时,这些库中的函数包含指向它们引用的任何符号的指针,而在Objective-C中,它们包含string表示这些符号的名字。 因此,在你的例子中,你可以使用dlsym来获取符号在内存中的地址,并将其附加到另一个符号。 库中的其他代码仍然有效,因为您不会更改原始符号的地址。 Objective-C使用查找表将类名映射到地址,它是一个1-1映射,所以不能有两个同名的类。 因此,要加载这两个类,其中一个类必须更改其名称。 但是,当其他类需要访问具有该名称的类之一时,它们将询问查找表的地址,并且查找表将永远不会返回给定原始类名称的重命名类的地址。
用一个唯一的前缀前缀你的类是基本上唯一的select,但有几种方法可以使这个不那么繁琐和丑陋。 这里有很多关于选项的讨论。 我最喜欢的是@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前缀所有的类,然后创build一个所有@compatibility_alias
(我想可以自动生成所述头)的标题。
像这样的前缀的缺点是你必须input真正的类名称(例如上面的COM_WHATEVER_ClassName
)在任何需要从编译器以外的string类名称的任何东西。 值得注意的是, @compatibility_alias
是一个编译器指令,而不是一个运行时函数,所以NSClassFromString(ClassName)
将失败(返回nil
) – 你将不得不使用NSClassFromString(COM_WHATERVER_ClassName)
。 您可以通过构build阶段使用ibtool
在Interface Builder nib / xib中修改类名,以便您不必在Interface Builder中编写完整的COM_WHATEVER _…。
最后需要注意的是:因为这是一个编译器指令(而且是一个不太明确的指令),它可能不能在编译器中移植。 特别是,我不知道LLVM项目是否可以与Clang前端一起工作,但是它应该与LLVM-GCC(使用GCC前端的LLVM)一起工作。
有几个人已经分享了一些可能有助于解决问题的棘手和巧妙的代码。 有些build议可能会奏效,但所有这些build议都不尽如人意,其中有些build议是彻头彻尾的。 (有时丑陋的黑客是不可避免的,但我尽量避免他们。)从实际的angular度来看,这里是我的build议。
- 无论如何,通知开发商这两个框架的冲突,并明确表示,他们未能避免和/或处理它是导致你真正的业务问题,这可能会转化为失去的业务收入,如果没有解决。 强调在解决现有冲突的基础上,不是每个class级都要修改前缀,而是完全改变前缀(如果不是现在就是使用一个,如果他们现在不是这样做的话,那就羞愧了)是确保他们不会再次看到同样的问题。
- 如果命名冲突仅限于相当小的一组类,请参阅是否可以解决这些类,特别是如果其中一个冲突类没有被代码直接或间接使用。 如果是,请查看供应商是否提供不包含冲突类的框架的定制版本。 如果不是这样,坦率地说,他们的灵活性正在降低您的投资回报率,使用他们的框架。 不要因为在合理的原因而被推feel而感到不快 – 客户永远是对的。 😉
- 如果一个框架更“可有可无”,那么您可以考虑用另一个框架(或代码组合)替代它,无论是第三方还是自制软件。 (后者是不可取的最坏情况,因为它肯定会招致开发和维护的额外业务成本。)如果是这样,请告知供应商该框架到底为什么你决定不使用他们的框架。
- 如果这两个框架对您的应用程序来说同样是不可或缺的,那么可以探索将其中一个框架用于一个或多个独立stream程的方法,也许可以像路易斯•盖巴格(D. 根据交stream的程度,这可能不会像你所期望的那样糟糕。 我相信有几个程序(包括QuickTime,我相信)使用这种方法来提供在Leopard中使用Seatbelt沙箱configuration文件提供的更细粒度的安全性,以便只允许您的代码的特定子集执行关键或敏感的操作。 性能将是一个折衷,但可能是你唯一的select
我猜测许可费用,期限和期限可能会阻止对这些问题的任何立即行动。 希望你能尽快解决冲突。 祝你好运!
这很重要,但是你可以使用分布式对象来保持其中一个类只在一个从属程序地址和RPC中。 如果你传递了大量的东西(如果两个类都直接操作视图等等,这可能是不可能的)。
还有其他可能的解决scheme,但是其中很多都取决于具体情况。 特别是,你使用的是现代还是传统的运行时,你是胖还是单一的体系结构,32位或者64位,你定位的操作系统是什么,你是dynamic链接,还是静态链接,还是有select的可能可以做一些可能需要维护新软件更新的东西。
如果你真的绝望,你可以做的是:
- 不直接链接到其中一个库
- 实现在加载时更改名称的objc运行时例程的替代版本(检出objc4项目,您需要做什么取决于我上面提到的一些问题,但不pipe答案是什么)。
- 使用类似mach_override的东西来注入新的实现
- 使用正常的方法加载新的库,它将通过修补的链接器例程,并更改其className
以上是相当劳动密集型的,如果你需要在多个拱形和不同的运行版本上实现它,这将是非常不愉快的,但是它绝对是可以工作的。
您是否考虑过使用运行时函数(/usr/include/objc/runtime.h)将其中一个冲突类克隆到非冲突类,然后加载冲突类框架? (这将需要在不同的时间加载碰撞的框架才能工作。)
你可以检查类的ivars,方法(具有名称和实现地址)和运行时的名称,并创build自己的dynamic拥有相同的伊娃布局,方法名称/实现地址,只有名称不同(避免碰撞)
绝望的情况需要绝望的措施。 你是否考虑过盗用其中一个库的目标代码(或库文件),将碰撞的符号改为另一个名称 – 长度相同但拼写不同(但推荐使用相同长度的名称)? 固然令人讨厌。
不清楚你的代码是直接调用两个名称相同但实现不同的函数,或者冲突是间接的(也不清楚是否有区别)。 但是,重命名至less有一个机会。 这也可能是一个想法,以最大限度地减less拼写的差异,所以如果符号在一个表中的sorting顺序,重命名不移动的事情失序。 像二进制search这样的事情,如果他们正在search的数组没有按照预期sorting的顺序,就会感到不安。
看来这个问题是你不能在同一个翻译单元(源文件)中引用来自两个系统的头文件。 如果你在库中创build了objective-c包装器(使得它们在进程中更加可用),并且在包装类的实现中只包含每个库的头文件,这将有效地分离名称冲突。
我没有足够的经验,在Objective-C(刚入门),但我相信这是我会在C
@compatibility_alias
将能够解决类名称空间冲突,例如
@compatibility_alias NewAliasClass OriginalClass;
但是, 这不会解决任何枚举,types定义或协议名称空间冲突 。 而且,对于原始类的@class前缀,它并不能很好地发挥作用。 由于大多数框架都会使用types定义这样的非类的东西,所以您可能无法使用compatibility_alias修复命名空间问题。
我查看了一个类似的问题 ,但是我有权访问源代码并正在构build框架。 我发现最好的解决scheme是使用@compatibility_alias
有条件地使用#defines来支持枚举/ @compatibility_alias
/ protocols / @compatibility_alias
你可以在有问题的头文件的编译单元上有条件地做到这一点,以最小化在其他碰撞框架中扩展东西的风险。
前缀文件是我知道的最简单的解决scheme。 Cocoadev有一个命名空间页面,这是一个社区努力避免命名空间冲突。 随意添加你自己的名单,我相信这是为了什么。
如果你碰到一个问题,我build议你认真考虑一下如何重构你的应用程序中的一个框架。 碰撞意味着两者正在做类似的事情,你可能只是通过重构你的应用程序就可以使用额外的框架。 这不仅可以解决你的命名空间问题,而且可以使你的代码更健壮,更容易维护,更高效。
在一个更技术性的解决scheme,如果我在你的位置这将是我的select。
如果碰撞仅在静态链接级别,那么您可以select使用哪个库来parsing符号:
cc foo.o -ldog bar.o -lcat
如果foo.o
和bar.o
都引用符号rat
那么libdog
将parsingfoo.o
的rat
, libcat
将parsinglibcat
的rat
。
只是一个想法..没有testing或certificate,可以成为标志的方式,但在你考虑写一个适配器的类从你的框架更简单的使用..或至less他们的接口?
如果你要编写一个简单的框架(或者你最less访问的接口)的封装,那么将这个封装器编译成一个库是不可能的。 鉴于库是预编译的,只有它的头文件需要分发,你将会有效地隐藏底层框架,并可以自由地将它与第二个框架相冲突。
当然,我很欣赏,可能有时候需要同时从两个框架中使用类,但是您可以为工厂提供该框架的其他类适配器。 在这一点的背后,我想你需要一些重构来从两个框架中提取出你正在使用的接口,这应该为你创build包装器提供一个很好的起点。
你可以像你一样在库上构build,当你需要包装库的更多function时,只要你改变了就重新编译。
再次,没有办法certificate,但感觉就像增加一个观点。 希望它有助于:)
如果你有两个具有相同函数名的框架,你可以尝试dynamic加载框架。 这将是不雅,但可能的。 如何与Objective-C类做到这一点,我不知道。 我猜测NSBundle
类将有加载一个特定的类的方法。