【问题标题】:Objective-C Frameworks and namespace clashesObjective-C 框架和命名空间冲突
【发布时间】:2020-02-17 16:18:47
【问题描述】:

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

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

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

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% 的类名在每个版本中都是相同的。

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

更新

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

我开始相信在目标 c 类名称前面加上某种宏可能是唯一的解决方案。大量共享代码使这成为一个严峻的前景。

有谁知道我可以隐藏类名以便只有每个框架都可以看到自己的类?

有没有更好的方法将一个应用的两个版本添加到同一个包中?甚至可能吗? Appstore 审核会有问题吗?

【问题讨论】:

    标签: ios objective-c ios-frameworks


    【解决方案1】:

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

    但是让我们假设您已经成功地对您的应用版本进行了框架化。

    如果您想在运行时选择其中一个框架,则不能链接到其中一个框架。相反,您需要使用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

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

    【讨论】:

    • 看起来很有希望,谢谢!一旦我有机会完成它,我就会将它标记为接受。还要感谢有关捆绑包的警告,我已经在我的测试用例中解决了这个问题,但是在我的实际应用程序中清除捆绑包的使用可能是另一回事。
    • 效果很好,非常感谢!实施您的答案后,唯一的额外步骤是从 TARGETS>General>“Linked frameworks and libraries”中删除框架。我以前从未听说过 dlopen(),我觉得我已经迈出了进入更大世界的第一步!
    • 您可能还需要添加 Copy Files 构建阶段以将它们复制到 Frameworks 文件夹,因为 Linked Frameworks 和 Libraries 构建阶段不再需要。在下次清理项目之前,您可能不会遇到这种情况。
    • 一个小问题。我已将我的应用程序的第 1 版和第 2 版制作成框架。在我的项目中,我有一个框架 v1 目标和一个启动器应用程序目标,并复制到框架 v2 中。启动器在启动时选择所需的框架。使用此设置,警告“Class XXX is implemented in both”会再次出现,并且始终使用框架 v1 中的类。如果我创建一个完全独立的启动器项目并在两个框架中复制,它运行良好。我还没有完全理解这一点。
    • 一个更大的问题是,在 LibA 上调用 dlclose() 后,尝试加载 LibB 将导致“Class XXX is implemented in both”警告。这意味着需要重新启动应用程序。希望我的老板/AppStore 评论者对此表示满意。
    猜你喜欢
    • 1970-01-01
    • 2013-07-14
    • 2012-12-18
    • 2023-04-09
    • 2021-11-22
    • 1970-01-01
    • 2014-07-01
    • 2011-08-06
    • 1970-01-01
    相关资源
    最近更新 更多