好的,所以基于阅读问题并提出一些问题......我真的不清楚为什么关于 pod 的答案会获得如此多的选票。并不能解决问题。
假设我们有现有的 ProjectA 和 ProjectB。 ProjectC 尚不存在,但希望使 ProjectC 成为 A 和 B 的“组合”,其中 A 或 B 将根据某些条件运行。运行后,应用程序将一直运行该版本,直到重新启动。
两种基本方法是将所有代码和资产组合到 ProjectC 中,或者尝试制作您加载的 A 和 B 框架。但是,对于任何一种情况,您都需要对代码库进行调整。您需要完成的工作量也是项目复杂程度的副产品。
我参与了一个项目,我们成功地完成了您对大型应用程序的要求。我们基本上通过将我们的 iPhone 和 iPad 项目结合代码/资产制作了一个“通用应用程序”。我们在运行时“启动”了适当的版本。
在你付出这样的努力之前,你需要权衡后果。我会列出一些。
- 如果您的 A/B 依赖于 bundle id,您就会遇到问题。 ProjectC 将拥有自己的捆绑包 ID。例如,是否有任何第 3 方 API(例如 Facebook)正在使用中,您仍然希望使用它们并且看起来像 ProjectA 或 ProjectB?如果是,但它们与捆绑包 ID 相关联,您就会遇到问题。
- 如果您使用 IAP,您的产品 ID 需要不同。因此,如果这些产品 ID 是硬编码的,那么显然 A 和 B 中的代码将需要更改。如果它们是服务器驱动的,您需要确保服务器代码并实际提供正确的产品 ID。如果预计 ProjectA 的所有者仍应拥有 ProjectC 的 IAP,这是可能的……但是成本需要根据您的规模来管理服务器端逻辑。
- 在调试代码时需要付出多少努力?
- ProjectA 和 ProjectB 是否仍在积极开发中?这样做可能会使这些项目以后很难维护。
- 为此要留出多少时间?这样做很乏味且需要时间。
您可能会更成功地将 A 和 B 的源/资产合并到 C 中。换句话说,您不会将 A 或 B 的项目文件添加到 C 中。为什么?因为您需要能够轻松识别所有冲突点,然后提供解决方法。一个简单的冲突示例是AppDelegate,这是项目默认创建的。您将拥有其中的 3 个(A、B、C)。
请记住,所有方法都充满问题。如果您选择框架(无论它们是否是 Pod)并且您决定将资产放入框架包中,则必须更改代码才能访问它们,因为它们不再位于 mainBundle 中。
好的,一般准则是什么?
- 选择一种方法(例如,组合或框架)。我将讨论结合。
- 预先确定尽可能多的冲突。为每种类型的冲突确定您的策略。例如,要解决
AppDelegate,您总是可以使用ProjectAAppDelegate 和ProjectBAppDelegate。
- 检查
Info.plist。这是其他冲突的一个很好的来源。 C 的Info.plist 将是两者的组合。
- 想出一个处理冲突的策略。例如,我们有一个命名约定,当我们有冲突的类名时会使用。
- 将您的 ProjectA 和 ProjectC 源代码/资产添加到 Project C 中。开始修复冲突。
- 多喝咖啡。
您需要控制的其他关键事项之一是基于决策的切入点。如果您可以在main.m 中调用UIApplicationMain 之前做出决定,您可以执行以下操作:
Class appDelegate;
if (runA) {
appDelegate = [ProjectAAppDelegate class];
} else {
appDelegate = [ProjectBAppDelegate class];
}
return UIApplicationMain(argc, argv, nil, NSStringFromClass(appDelegate));
如果这不起作用,那么您必须在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 中执行此操作。您可能希望项目 C 的 AppDelegate 成为 A 和 B 的代理。例如:
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
[self.projectAppDelegate applicationDidBecomeActive:application];
}
projectAppDelegate 设置为您需要的正确版本。
请注意,您还需要手动加载情节提要以确保启动正确的情节提要。请记住,如果您在加载过程中花费大量时间做出决定(您说这是一次网络调用),您的应用程序可能会被启动。
如果它们是框架,您也许可以找到一些可以执行的漂亮动态加载,但关键应该是可维护性/易于调试。
我要在这里停下来主要是因为有很多不同的事情要做,我真的没有时间把它们都写出来。
Additional info based on follow up question
您将有一些文件重复的可能性很高。我举了AppDelegate的例子。 enum 将不那么频繁,但会发生。还要记住,它不是您关心的文件名,而是您关心的类或其他定义的数据类型。这些是对编译器/链接器很重要的冲突。
比如说:
项目A:
typedef NS_ENUM(NSInteger, State) {}
项目B:
typedef NS_ENUM(NSInteger, State) {}
这就是我提前制定策略的意思。示例策略:
- ProjectA 是“赢家”,因此只有 ProjectB 会有变化
- ProjectB 是“赢家”,因此只有 ProjectA 会有变化
- 项目 A 和项目 B 都会发生变化。
所以对于 1,结果是
项目A:
typedef NS_ENUM(NSInteger, State) {}
项目B:
typedef NS_ENUM(NSInteger, PBState) {}
注意我听到了什么。为了让我的生活更轻松,我使用了一些预定义的前缀来指定它是 ProjectB。在这种情况下是 PB。
对于 2:
项目A:
typedef NS_ENUM(NSInteger, PAState) {}
项目B:
typedef NS_ENUM(NSInteger, State) {}
对于 3:
项目A:
typedef NS_ENUM(NSInteger, PAState) {}
项目B:
typedef NS_ENUM(NSInteger, PBState) {}
由您来制定规则,但要保持一致。
您将需要一个策略来解决所有数据类型更改。例如,如果您从State -> PBState 出发,您显然只想修改 ProjectB。这是将它放在自己的项目中会有所帮助的地方。不过,您可以使用 Xcode 的 Search Scope 来帮助控制这一点。
哦,还有一些其他的事情。
首先,投资一个脚本来查找 ProjectA 和 ProjectB 中的所有重复文件。您基本上需要根据您需要的扩展名(例如.m、.h、.xib 等)在 ProjectA 和 ProjectB 上执行find。这将为您提供潜在候选人列表,您可以从那里制定规则。
由于我是完成这部分项目的傻瓜,我基本上将这个列表保存在一个文本文件中。当我合并文件时,我将它移动到文件中的不同部分(由几个换行符分隔)。记账的方法有很多种,只是我选择的方法。
我也会确保你有一个很好的差异工具,比如 Araxis Merge(这是我使用的)。
另外,请经常拍摄快照以防万一。您可以使用 git 分支。我经常只是复制实际的目录,以便以后在需要时对它们进行比较。