Objective-C 框架和命名空间冲突

Objective-C Frameworks and namespace clashes

我们想将 iOS 应用程序的两个版本放在同一个包中。这样我们的客户可以在推送更新后恢复到旧版本。我曾希望通过将当前和以前的版本构建到框架中并在提示用户后调用适当的版本来实现这一点。

作为测试,我创建了两个框架,LibA 和 LibB,每个都包含 class 事物。

我遇到的问题是这个运行时警告...

objc[21117]: Class Thing is implemented in both /private/var/containers/Bundle/Application/0E6374C5-52FB-421F-90D6-ADC9A4C22B5D/DualBootTestApp.app/Frameworks/LibA.framework/LibA (0x102b144b0) and /private/var/containers/Bundle/Application/0E6374C5-52FB-421F-90D6-ADC9A4C22B5D/DualBootTestApp.app/Frameworks/LibB.framework/LibB (0x102ba4460). One of the two will be used. Which one is undefined.

在现实世界中,这些框架将是同一应用程序的两个版本,因此 99% 的 class 名称在每个版本中都是相同的。

每个框架实际上都调用自己版本的 Thing,但运行时警告提示我不能依赖该行为。

更新

我刚刚尝试使用 Cocoa Touch 静态库。使用静态库时,我不会收到运行时警告,但始终会调用来自 LibB 的 class Thing 版本,即使调用来自 LibA。

我开始相信在 objective c class 名称前加上某种宏前缀可能是唯一的解决方案。许多共享代码使这成为一个严峻的前景。

有谁知道我可以隐藏 class 名称的方法,这样只有每个框架都可以看到自己的 classes 吗?

是否有更好的方法将一个应用程序的两个版本添加到同一个包中?有可能吗? Appstore审核会不会有问题?

如果您已经将 类 制作成框架,恭喜,您已经完成了困难的部分。但是请记住,如果您的大部分应用程序代码都在一个框架中,那么某些事情可能不会按预期运行。例如,任何最终调用 NSBundle.mainBundle(例如 +[UIImage imageNamed:])的代码都可能是错误的,假设您也对资产进行了版本控制。

但我们假设您已成功将您的应用程序版本框架化。

如果您想在运行时选择其中一个框架,则不能 link 反对任何一个框架。相反,您需要使用 dlopenNSClassFromStringdlsym 来到达入口点。

这是一个例子:

#import <dlfcn.h>
#import "HeaderWithEntryPointMethod.h"

void pickedAppVersion(int version) {
    NSString *frameworkName = [NSString stringWithFormat:@"AppV%d", version];
    NSString *frameworkExecutable = [NSString stringWithFormat:@"%@.framework/%@", frameworkName, frameworkName]; // this should traverse the symlink 
    NSString *frameworkPath = [[[NSBundle mainBundle] privateFrameworksPath] stringByAppendingPathComponent:frameworkExecutable];
    void *frameworkHandle = dlopen(frameworkPath.UTF8String, RTLD_NOW);
    if (frameworkHandle != NULL) {
        Class EntryPointClass = NSClassFromString(@"EntryPoint");
        assert(EntryPointClass != Nil);
        [EntryPointClass entryMethod];
        // App framework should do everything from here
        if (dlclose(frameworkHandle) != 0) {
            NSLog(@"failed to close chosen app framework: %s", dlerror());
        }
    }
    else {
        NSLog(@"failed to open app framework: %@ because: %s", frameworkName, dlerror());
    }
}

至于 EntryPoint+entryMethod 究竟是什么,这取决于您。如果您需要 C 函数入口点,请使用 dlsym 而不是 NSClassFromString

回复:App Store 评论:我想这可能会引起一两个人的注意,但只要您允许用户,尤其是评论者选择,就不会有问题。 dlopen 通常用于在运行时有选择地加载框架,以简化应用程序启动和加载应用程序按需使用的功能。