karthik karthik - 2 months ago 5
iOS Question

How to run a project out of 2 embedded different project

I have two iOS projects and I want to make it as a single project and run one project at a time depending on a condition.

For example : I have 2 projects named ProjectA and ProjectB.I want to embed ProjectA and ProjectB into a single project named ProjectC.
ProjectC will have a condition depending on the condition I have to run either projectA or ProjectB code.

Note: Condition to run a project will be applied on runtime not on compile time.

I doubt is this really possible ? I need expertise guidance on how to approach this problem.

Answer

Okay, so based on reading the question and asking some questions ... it really isn't clear to me why the answer regarding pods has gotten so many votes. It does not solve the problem.

Let's say we have existing ProjectA and ProjectB. ProjectC is not yet in existence, but the desire is to make ProjectC be a "combination" of A and B where A or B will be run based on some condition. Once run, the app will stay running that version until re-launched.

Two basic approaches would be to either combine all the code and assets into ProjectC or trying to make A and B frameworks which you load. However, with either scenario, you are going to have make adjustments to your code base. The amount of work you will need to do is also a byproduct of how complicated the projects are.

I've worked on a project where we successfully did what you wanted for a large app. We essentially made a "universal app" by taking our iPhone and iPad projects, combined the code/assets. We "launched" the appropriate version at runtime.

Before you go down such an effort, you will need to weigh the consequences. I'll list a few.

  • If you have A/B have a dependency on bundle id, you're going to have issues. ProjectC will have it's own bundle id. For example are there any 3rd Party APIs (eg. Facebook) in use where you would want to still use them and look like ProjectA or ProjectB? If yes, but they are tied to bundle id, you'll have issues.
  • If you are using IAP, your product ids will need to be different. So obviously the code in A and B will need to change if those product ids are hardcoded. If they are server driven you need to ensure the server code and actually deliver the right product ids. If there is an expectation that an owner of ProjectA should still have their IAP for ProjectC, this is possible ... however the cost comes to needing server side logic on your size to manage this.
  • How much effort do you want when debugging the code?
  • Are ProjectA and ProjectB still in active development? Doing this may very much make those projects very difficult to maintain afterwards.
  • How much time will be set aside for this? Doing this is tedious and takes time.

You would probably get more success combining source/assets of both A and B into C. In other words you will not be adding the project files for A or B into C. Why? Because you need to be able to easily identify all the conflict points and then provide a work around. A simple example of a conflict is AppDelegate, which is what project creates by default. You'll have 3 of them (A, B, C).

Keep in mind all approaches are fraught with issues. If you go for frameworks (regardless if they are Pods or not) and you decide your assets go into the framework bundle, you have to change your code to access them, as they are no longer in the mainBundle.

Okay, what are the general guidelines?

  • Choose a methodology (eg. combine or framework). I am going to discuss combine.
  • Determine as much of your conflicts up front. For each type of conflict determine your strategy. For example, to solve AppDelegate you could always do ProjectAAppDelegate and ProjectBAppDelegate.
  • Examine the Info.plist. This is a good source of other conflicts. C's Info.plist will be a combination of the two.
  • Come up with a strategy for how you will deal with conflicts. For example, we had a naming convention we would use when we had class names that conflicted.
  • Add your ProjectA and ProjectC source code/asset into Project C. Start to fix conflicts.
  • Drink a lot of coffee.

One of your other key things you will need to get control of is your entry point based on the decision. If you can decide before calling UIApplicationMain in main.m, you could do something like:

    Class appDelegate;

    if (runA) {
        appDelegate = [ProjectAAppDelegate class];
    } else {
        appDelegate = [ProjectBAppDelegate class];

    }

    return UIApplicationMain(argc, argv, nil, NSStringFromClass(appDelegate));

IF this does not work, then you'll have to do this in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions. You'll probably want to then have Project C's AppDelegate be a proxy for A and B. For example:

- (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];
}

where projectAppDelegate was set to the right version you need.

Note you will also need to manually load your storyboard to make sure the right one is launched. And keep in mind that if you spend to much time making your decision during loading (you said it was a network call) your app can be booted.

You may be able to find some nifty dynamic loading you could do if they were frameworks, but the key should be maintainability/ease of debugging.

I'm going to stop here mainly because there are so many different things to do and I really I don't have the time to write them all out.

Additional info based on follow up question

Odds are high that you will have some file duplication. I gave the example of AppDelegate. enum will be less frequent, but will occur. Also keep in mind, it is not the file name you care about, it is the class or other defined data type you are concerned about. Those are the conflicts that matter to the compiler/linker.

For example say:

ProjectA:

typedef NS_ENUM(NSInteger, State) {}

Project B:

typedef NS_ENUM(NSInteger, State) {}

This was what I meant about having a strategy ahead of time. Example strategies:

  1. ProjectA is the "winner", and hence only ProjectB will have changes
  2. ProjectB is the "winner", and hence only ProjectA will have changes
  3. Both Project A and Project B will have changes.

So for 1, the result would be

ProjectA:

typedef NS_ENUM(NSInteger, State) {}

Project B:

typedef NS_ENUM(NSInteger, PBState) {}

Note what I did hear. To make my life easier I used some pre-defined prefix to designate it is ProjectB. In this case PB.

For 2: ProjectA:

typedef NS_ENUM(NSInteger, PAState) {}

Project B:

typedef NS_ENUM(NSInteger, State) {}

For 3: ProjectA:

typedef NS_ENUM(NSInteger, PAState) {}

Project B:

typedef NS_ENUM(NSInteger, PBState) {}

It will be up to you to make the rules, but be consistent. You will need a strategy for how you will resolve all the data type changes. For example, if you are going from State -> PBState, you obviously only want to modify ProjectB. Here is where having it in it's own project would help. However, you can use Xcode's Search Scope to help control this.

Oh some other things.

Up front, invest on a script to find all the duplicate files in ProjectA and ProjectB. You basically need to do a find on ProjectA and ProjectB based on the extensions you need (eg. .m, .h, .xib, etc). This will give you a list of potential candidates and from there you can strategize on rules.

As I was the sucker that did this part of the project, I basically kept this list in a text file. When I consolidated the file, I moved it to a different section (separated by several newlines) in the file. There are diff ways of doing the accounting on it, it is simply the method I chose.

I would also make sure you have a good diff tool like Araxis Merge (which is what I used).

Also, take frequent snapshots just in case. You can use git branching. I often just copied the actual directories so I could diff them later if needed.

Comments