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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/178434/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 20:48:32  来源:igfitidea点击:

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

objective-ccocoamacosnamespaces

提问by Mecki

Objective-C has no namespaces; it's much like C, everything is within one global namespace. Common practice is to prefix classes with initials, e.g. if you are working at IBM, you could prefix them with "IBM"; if you work for Microsoft, you could use "MS"; and so on. Sometimes the initials refer to the project, e.g. Adium prefixes classes with "AI" (as there is no company behind it of that you could take the initials). Apple prefixes classes with NS and says this prefix is reserved for Apple only.

Objective-C 没有命名空间;它很像 C,一切都在一个全局命名空间中。通常的做法是在类前加上首字母,例如,如果你在 IBM 工作,你可以用“IBM”作为前缀;如果你为微软工作,你可以使用“MS”;等等。有时首字母是指项目,例如 Adium 以“AI”作为类的前缀(因为它背后没有公司可以使用首字母)。Apple 使用 NS 为类添加前缀,并表示此前缀仅供 Apple 使用。

So far so well. But appending 2 to 4 letters to a class name in front is a very, very limited namespace. E.g. MS or AI could have an entirely different meanings (AI could be Artificial Intelligence for example) and some other developer might decide to use them and create an equally named class. Bang, namespace collision.

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

Okay, if this is a collision between one of your own classes and one of an external framework you are using, you can easily change the naming of your class, no big deal. But what if you use two external frameworks, both frameworks that you don't have the source to and that you can't change?Your application links with both of them and you get name conflicts. How would you go about solving these? What is the best way to work around them in such a way that you can still use both classes?

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

In C you can work around these by not linking directly to the library, instead you load the library at runtime, using dlopen(), then find the symbol you are looking for using dlsym() and assign it to a global symbol (that you can name any way you like) and then access it through this global symbol. E.g. if you have a conflict because some C library has a function named open(), you could define a variable named myOpen and have it point to the open() function of the library, thus when you want to use the system open(), you just use open() and when you want to use the other one, you access it via the myOpen identifier.

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

Is something similar possible in Objective-C and if not, is there any other clever, tricky solution you can use resolve namespace conflicts? Any ideas?

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



Update:

更新:

Just to clarify this: answers that suggest how to avoid namespace collisions in advance or how to create a better namespace are certainly welcome; however, I will not accept them as the answersince they don't solve my problem. I have two libraries and their class names collide. I can't change them; I don't have the source of either one. The collision is already there and tips on how it could have been avoided in advance won't help anymore. I can forward them to the developers of these frameworks and hope they choose a better namespace in the future, but for the time being I'm searching a solution to work with the frameworks right now within a single application. Any solutions to make this possible?

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

采纳答案by Michael Buckley

If you do not need to use classes from both frameworks at the same time, and you are targeting platforms which support NSBundle unloading (OS X 10.4 or later, no GNUStep support), and performance really isn't an issue for you, I believe that you could load one framework every time you need to use a class from it, and then unload it and load the other one when you need to use the other framework.

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

My initial idea was to use NSBundle to load one of the frameworks, then copy or rename the classes inside that framework, and then load the other framework. There are two problems with this. First, I couldn't find a function to copy the data pointed to rename or copy a class, and any other classes in that first framework which reference the renamed class would now reference the class from the other framework.

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

You wouldn't need to copy or rename a class if there were a way to copy the data pointed to by an IMP. You could create a new class and then copy over ivars, methods, properties and categories. Much more work, but it is possible. However, you would still have a problem with the other classes in the framework referencing the wrong class.

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

EDIT: The fundamental difference between the C and Objective-C runtimes is, as I understand it, when libraries are loaded, the functions in those libraries contain pointers to any symbols they reference, whereas in Objective-C, they contain string representations of the names of thsoe symbols. Thus, in your example, you can use dlsym to get the symbol's address in memory and attach it to another symbol. The other code in the library still works because you're not changing the address of the original symbol. Objective-C uses a lookup table to map class names to addresses, and it's a 1-1 mapping, so you can't have two classes with the same name. Thus, to load both classes, one of them must have their name changed. However, when other classes need to access one of the classes with that name, they will ask the lookup table for its address, and the lookup table will never return the address of the renamed class given the original class's name.

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

回答by Barry Wark

Prefixing your classes with a unique prefix is fundamentally the only option but there are several ways to make this less onerous and ugly. There is a long discussion of options here. My favorite is the @compatibility_aliasObjective-C compiler directive (described here). You can use @compatibility_aliasto "rename" a class, allowing you to name your class using FQDN or some such prefix:

使用唯一前缀为类添加前缀基本上是唯一的选择,但有几种方法可以使这变得不那么繁琐和丑陋。还有就是选择一个长时间的讨论在这里。我最喜欢的是@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

As part of a complete strategy, you could prefix all your classes with a unique prefix such as the FQDN and then create a header with all the @compatibility_alias(I would imagine you could auto-generate said header).

作为完整策略的一部分,您可以使用唯一前缀(例如 FQDN)为所有类添加前缀,然后使用所有前缀创建标头@compatibility_alias(我想您可以自动生成所述标头)。

The downside of prefixing like this is that you have to enter the true class name (e.g. COM_WHATEVER_ClassNameabove) in anything that needs the class name from a string besides the compiler. Notably, @compatibility_aliasis a compiler directive, not a runtime function so NSClassFromString(ClassName)will fail (return nil)--you'll have to use NSClassFromString(COM_WHATERVER_ClassName). You can use ibtoolvia build phase to modify class names in an Interface Builder nib/xib so that you don't have to write the full COM_WHATEVER_... in Interface Builder.

像这样添加前缀的缺点是,COM_WHATEVER_ClassName除了编译器之外,您必须在需要来自字符串的类名的任何内容中输入真正的类名(例如上面)。值得注意的是,@compatibility_alias是一个编译器指令,而不是一个运行时函数,所以NSClassFromString(ClassName)会失败(返回nil)——你必须使用NSClassFromString(COM_WHATERVER_ClassName). 您可以使用ibtoolvia build phase 来修改 Interface Builder nib/xib 中的类名,这样您就不必在 Interface Builder 中编写完整的 COM_WHATEVER_...。

Final caveat: because this is a compiler directive (and an obscure one at that), it may not be portable across compilers. In particular, I don't know if it works with the Clang frontend from the LLVM project, though it should work with LLVM-GCC (LLVM using the GCC frontend).

最后警告:因为这是一个编译器指令(并且是一个模糊的指令),它可能无法跨编译器移植。特别是,我不知道它是否适用于 LLVM 项目的 Clang 前端,但它应该适用于 LLVM-GCC(使用 GCC 前端的 LLVM)。

回答by Quinn Taylor

Several people have already shared some tricky and clever code that might help solve the problem. Some of the suggestions may work, but all of them are less than ideal, and some of them are downright nasty to implement. (Sometimes ugly hacks are unavoidable, but I try to avoid them whenever I can.) From a practical standpoint, here are my suggestions.

一些人已经分享了一些可能有助于解决问题的棘手而聪明的代码。一些建议可能会奏效,但所有建议都不太理想,而且其中一些建议实施起来非常糟糕。(有时难看的 hack 是不可避免的,但我会尽量避免它们。)从实用的角度来看,以下是我的建议。

  1. In any case, inform the developers of bothframeworks of the conflict, and make it clear that their failure to avoid and/or deal with it is causing you real business problems, which could translate into lost business revenue if unresolved. Emphasize that while resolving existing conflicts on a per-class basis is a less intrusive fix, changing their prefix entirely (or using one if they're not currently, and shame on them!) is the best way to ensure that they won't see the same problem again.
  2. If the naming conflicts are limited to a reasonably small set of classes, see if you can work around just those classes, especially if one of the conflicting classes isn't being used by your code, directly or indirectly. If so, see whether the vendor will provide a custom version of the framework that doesn't include the conflicting classes. If not, be frank about the fact that their inflexibility is reducing your ROI from using their framework. Don't feel bad about being pushy within reason — the customer is always right. ;-)
  3. If one framework is more "dispensable", you might consider replacing it with another framework (or combination of code), either third-party or homebrew. (The latter is the undesirable worst-case, since it will certainly incur additional business costs, both for development and maintenance.) If you do, inform the vendor of that framework exactly why you decided to not use their framework.
  4. If both frameworks are deemed equally indispensable to your application, explore ways to factor out usage of one of them to one or more separate processes, perhaps communicating via DO as Louis Gerbarg suggested. Depending on the degree of communication, this may not be as bad as you might expect. Several programs (including QuickTime, I believe) use this approach to provide more granular security provided by using Seatbelt sandbox profiles in Leopard, such that only a specific subset of your code is permitted to perform critical or sensitive operations. Performance will be a tradeoff, but may be your only option
  1. 在任何情况下,将冲突告知两个框架的开发人员,并明确说明他们未能避免和/或处理冲突正在给您带来真正的业务问题,如果不解决,这可能会转化为业务收入的损失。强调虽然在每个类的基础上解决现有的冲突是一种较少侵入性的修复,但完全改变它们的前缀(或者如果它们当前没有,则使用一个,对它们感到羞耻!)是确保它们不会发生的最好方法再次看到同样的问题。
  2. 如果命名冲突仅限于相当少的一组类,请查看您是否可以只解决这些类,尤其是当您的代码没有直接或间接使用其中一个冲突类时。如果是这样,请查看供应商是否会提供不包含冲突类的框架的自定义版本。如果没有,坦率地说,他们的不灵活性正在降低您使用他们的框架的投资回报率。不要因为在合理范围内咄咄逼人而感到难过——客户永远是对的。;-)
  3. 如果一个框架更“可有可无”,您可以考虑用另一个框架(或代码组合)替换它,第三方或自制软件。(后者是不受欢迎的最坏情况,因为它肯定会产生额外的业务成本,包括开发和维护。)如果这样做,请告知该框架的供应商您决定不使用他们的框架的确切原因。
  4. 如果认为这两个框架对您的应用程序同样不可或缺,请探索将其中一个框架的使用分解为一个或多个单独进程的方法,也许像 Louis Gerbarg 建议的那样通过 DO 进行通信。根据沟通的程度,这可能没有你想象的那么糟糕。有几个程序(我相信包括 QuickTime)使用这种方法来提供通过在 Leopard 中使用Seatbelt 沙箱配置文件提供的更细粒度的安全性,这样只允许您的代码的特定子集执行关键或敏感操作。性能将是一种权衡,但可能是您唯一的选择

I'm guessing that licensing fees, terms, and durations may prevent instant action on any of these points. Hopefully you'll be able to resolve the conflict as soon as possible. Good luck!

我猜测许可费用、条款和期限可能会阻止对这些要点中的任何一点立即采取行动。希望您能够尽快解决冲突。祝你好运!

回答by Louis Gerbarg

This is gross, but you could use distributed objectsin order to keep one of the classes only in a subordinate programs address and RPC to it. That will get messy if you are passing a ton of stuff back and forth (and may not be possible if both class are directly manipulating views, etc).

这很糟糕,但您可以使用分布式对象,以便仅在从属程序地址和 RPC 中保留一个类。如果您来回传递大量内容,那会变得混乱(如果两个类都直接操作视图等,则可能无法实现)。

There are other potential solutions, but a lot of them depend on the exact situation. In particular, are you using the modern or legacy runtimes, are you fat or single architecture, 32 or 64 bit, what OS releases are you targeting, are you dynamically linking, statically linking, or do you have a choice, and is it potentially okay to do something that might require maintenance for new software updates.

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

If you are really desperate, what you could do is:

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

  1. Not link against one of the libraries directly
  2. Implement an alternate version of the objc runtime routines that changes the name at load time (checkout the objc4project, what exactly you need to do depends on a number of the questions I asked above, but it should be possible no matter what the answers are).
  3. Use something like mach_overrideto inject your new implementation
  4. Load the new library using normal methods, it will go through the patched linker routine and get its className changed
  1. 不直接链接库之一
  2. 实现在加载时更改名称的 objc 运行时例程的替代版本(检查objc4项目,您究竟需要做什么取决于我上面提出的许多问题,但无论答案是什么,它都应该是可能的)。
  3. 使用类似mach_override 的东西来注入你的新实现
  4. 使用普通方法加载新库,它将通过修补的链接器例程并更改其 className

The above is going to be pretty labor intensive, and if you need to implement it against multiple archs and different runtime versions it will be very unpleasant, but it can definitely be made to work.

以上将是非常劳动密集型的,如果您需要针对多个架构和不同的运行时版本来实现它,这将非常令人不快,但它绝对可以工作。

回答by xtophyr

Have you considered using the runtime functions (/usr/include/objc/runtime.h) to clone one of the conflicting classes to a non-colliding class, and then loading the colliding class framework? (this would require the colliding frameworks to be loaded at different times to work.)

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

You can inspect the classes ivars, methods (with names and implementation addresses) and names with the runtime, and create your own as well dynamically to have the same ivar layout, methods names/implementation addresses, and only differ by name (to avoid the collision)

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

回答by Jonathan Leffler

Desperate situations call for desperate measures. Have you considered hacking the object code (or library file) of one of the libraries, changing the colliding symbol to an alternative name - of the same length but a different spelling (but, recommendation, the same length of name)? Inherently nasty.

绝望的情况需要绝望的措施。您是否考虑过破解其中一个库的目标代码(或库文件),将碰撞符号更改为另一个名称 - 长度相同但拼写不同(但建议使用相同长度的名称)?天生恶心。

It isn't clear if your code is directly calling the two functions with the same name but different implementations or whether the conflict is indirect (nor is it clear whether it makes any difference). However, there's at least an outside chance that renaming would work. It might be an idea, too, to minimize the difference in the spellings, so that if the symbols are in a sorted order in a table, the renaming doesn't move things out of order. Things like binary search get upset if the array they're searching isn't in sorted order as expected.

目前尚不清楚您的代码是否直接调用具有相同名称但实现不同的两个函数,或者冲突是否是间接的(也不清楚是否有任何区别)。但是,至少有外部机会重命名会起作用。最小化拼写差异也可能是一个想法,这样如果符号在表格中按排序顺序排列,重命名不会使内容无序。如果他们正在搜索的数组没有按预期排序,则诸如二分搜索之类的事情就会变得混乱。

回答by Michael Chinen

@compatibility_aliaswill be able to solve class namespace conflicts, e.g.

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

@compatibility_alias NewAliasClass OriginalClass;

However, this will not resolve any of the enums, typedefs, or protocol namespace collisions. Furthermore, it does not play well with @classforward decls of the original class. Since most frameworks will come with these non-class things like typedefs, you would likely not be able to fix the namespacing problem with just compatibility_alias.

但是,这不会解决任何枚举、类型定义或协议命名空间冲突。此外,它不能很好地与@class原始类的前向声明配合使用。由于大多数框架都带有这些非类的东西,例如 typedef,因此您可能无法仅使用 compatible_alias 来解决命名空间问题。

I looked at a similar problem to yours, but I had access to source and was building the frameworks. The best solution I found for this was using @compatibility_aliasconditionally with #defines to support the enums/typedefs/protocols/etc. You can do this conditionally on the compile unit for the header in question to minimize risk of expanding stuff in the other colliding framework.

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

回答by chrish

It seems that the issue is that you can't reference headers files from both systems in the same translation unit (source file). If you create objective-c wrappers around the libraries (making them more usable in the process), and only #include the headers for each library in the implementation of the wrapper classes, that would effectively separate name collisions.

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

I don't have enough experience with this in objective-c (just getting started), but I believe that is what I would do in C.

我在objective-c中没有足够的经验(刚刚开始),但我相信这就是我在c中会做的事情。

回答by mark

Just a thought.. not tested or proven and could be way of the mark but in have you considered writing an adapter for the class's you use from the simpler of the frameworks.. or at least their interfaces?

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

If you were to write a wrapper around the simpler of the frameworks (or the one who's interfaces you access the least) would it not be possible to compile that wrapper into a library. Given the library is precompiled and only itsheaders need be distributed, You'd be effectively hiding the underlying framework and would be free to combine it with the second framework with clashing.

如果您要围绕较简单的框架(或您访问最少的接口)编写包装器,则不可能将该包装器编译成库。鉴于该库是预编译的,并且只需要分发它的头文件,您将有效地隐藏底层框架,并且可以自由地将它与具有冲突的第二个框架结合起来。

I appreciate of course that there are likely to be times when you need to use class's from both frameworks at the same time however, you could provide factories for further class adapters of that framework. On the back of that point I guess you'd need a bit of refactoring to extract out the interfaces you are using from both frameworks which should provide a nice starting point for you to build your wrapper.

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

You could build upon the library as you and when you need further functionality from the wrapped library, and simply recompile when you it changes.

您可以根据自己的需要以及当您需要来自包装库的更多功能时构建库,并在更改时简单地重新编译。

Again, in no way proven but felt like adding a perspective. hope it helps :)

同样,无论如何都没有证明,但感觉像是添加了一个视角。希望能帮助到你 :)

回答by wcochran

If the collision is only at the static link level then you can choose which library is used to resolve symbols:

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

cc foo.o -ldog bar.o -lcat

If foo.oand bar.oboth reference the symbol ratthen libdogwill resolve foo.o's ratand libcatwill resolve bar.o's rat.

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