Let’s imagine I have this great MyBookshelf App and now with OS 3.0 coming I am planning to include Storekit so that my users can also purchase new content from within my app. So far, that’s a great idea. Additional features for my customers, additional income through in-app sales.
But maybe I have hundreds of thousands of users who are not immediately willing to upgrade to the new software. The reasons for this are outside the scope of this article but as an active developer you are risking loss of customers if you only concentrate on the new.
Your choices are:
- make a final build for 2.x putting in all the bugfixes you can and then move on. Existing 2.x customers will only receive the latest 2.x version, new customers will have to have a 3.0 iPhone to purchase the new app.
- create a new app from the same codebase adding only the 3.0 features there and then continuing to push updates to both the 2.x version and the 3.0 version.
- create magic code to dynamically load 3.0 frameworks if they are present. This is highly advanced and only for true pros.
For this article I am focussing on option 2. I want one project to hold my source code and have two targets, one for 2.x and one for 3.0. Only the 3.0 version can include the appropriate 3.0-frameworks and I don’t want to see any compiler problems. Also I don’t want to resort to some fancy art of dynamic loading.
I already used this method for LuckyWheel, where I am building the Lite version from the same source code. There are only minor modifications: icon, logo, info.plist and an extra script build step that removes the non-Lite content from the final distribution bundle. Here I am going to show that you can do the same thing with frameworks.
Step 1 – Prep your existing Project
Have a look at your project first. It’s already building nicely against SDK 2.1, that’s my standard. Expand the target node and notice that inside there you can see all the work that goes into building your app. Bundle resources are copied, then the code is compiled and finally the resulting binary is linked with the libraries.
Note that because this target was automatically created for you in the project template all is set up correctly here. Also every time you added a new file it was automatically added to this target.
Step 2 – Add new Target
A target is basically a container that tells XCode which pieces belong to a certain output app. By having mutliple targets you can mix and match pieces as you choose from within the same project. The easiest way is to just duplicate the existing target with right-click – duplicate and then renaming it from “MyBookshelf copy” to “MyBookshelf+”.
XCode is so helpful as to also copy the resource links. Actually the new target is completely identical to the first one. If you would switch target and build it now, you would end up overwriting the product of the first target. So we need to change the name of the app bundle as well. This is done in the “Get Info” – build dialog.
Modify the line “Product Name” and notice that this will also modify two other lines. Only change bold values as the other ones are mostly derived and you would add confusion for yourself should you change those. There is another important value there, the real name of the info.plist.
Step 3 – Adapt the info.plist, or not
Amongst other things the info.plist contains the display name of your app. Since this is currently an identical copy of the original we might think that we have to do huge modifications there, but this is not the case. All values with curly braces are derived from the target settings, so the only value to modify might be a different name for the icon file instead of the default “Icon.png” which is taken if this value is blank.
There is however one crucial thing to make sure: Both your targets have to have a different bundle identifier. This value uniquely identifies your app on an iPhone and if both are the same then the OS assumes that it must be the same app. Because it is derived per default you should not need to change it though.
But I just found a restrictions which bundle identifiers you can legally use. The plus sign is an illegal character here, so I had to manually modify it to com.yourcompany.MyBookShelfPlus to build.
Step 4 – Custom Content
Lets assume that I did not change the info.plist and I have two app icons for MyBookshelf and MyBookshelf+. You don’t even have to rename when you add them to the project. XCode will rename duplicate names automatically while preserving the name in the final build bundle. But for sake of clarity I recommend you put the content for the Plus version into a seperate group or even folder.
If you add a new or existing file you get a dialog that allows you to assign this file to one or more targets.
If you add one Icon.png to MyBookshelf and the other Icon.png to MyBookshelf+ then both resulting apps will have an Icon.png in their bundle, but always the correct one.
This technique can be used to the extreme if you have twenty similar apps, always the same engine, but completely different assets.
Step 5 – Custom Code
For custom code you can either use the same approach as in step 4 and have distinct source code files of the same name but assigned to different targets. The more elegant approach though is to use the same files but use the #ifdef pre-compiler macros. This will change the code before it reaches the compiler and thus you can have different parts of the same source code turn on or off.
Do define a define, got into the settings for your target, find the “GCC 4.0 Language” section and modify the “Other C Flags” line. If am adding a define for PLUSVERSION, the -D before it makes it one. This setting is local to the MyBookshelf+ target only, so this is perfect for us. Alternatively you could use predefined defines like Andreas Hecks has shown in his article “Handling Deprecated Methods for iPhone SDK 3.0“.
If you don’t see the GCC 4.0 section then you have to switch the list box to Project Setting or simply change it.
This this define set up now everywhere we have special code for this target we can do the following:
#ifdef PLUSVERSION // do fancy StoreKit stuff, that's still under NDA #endif
Step 6 – Custom Framework Linking
Now we want to add the StoreKit framework so that MyBookshelf+ can half this special ability. Add – Existing Framework and choose StoreKit.framework. If the framework is red then this is because when adding it you still had 2.0 as active SDK. XCode is looking for it in the 2.0 SDK path where clearly it cannot be found. But because we added it only to the Plus version still both targets can be built.
If you don’t like this red, remove the framework, switch the whole project to 3.0, then re-add the framework. To make sure you have assigned it to the right target, you can always “Get Info – Targets” of a framework.
I set the “Base SDK” to “Device – iPhone OS 3.0” for MyBookshelf+ and to “Device – iPhone OS 2.0” for MyBookshelf. Other combinations cannot be built because naturally the frameworks are taken from the path that corresponds to the base SDK. Also when building make sure you select “Project Setting” so that the SDK to be used comes from the target settings and not the listbox.
Step 7 – Build
In the end you build your apps. Switch to target MyBookshelf, build. Switch to target MyBookshelf+ and build again. Inspect the verbose build results to see which deployment target was set for linking and which frameworks where linked in.
MyBookshelf for OS 2.0, no StoreKit framework:
Ld /Users/Oliver/Desktop/MyBookshelf/build/Debug-iphoneos/MyBookshelf.app/MyBookshelf normal armv6 cd /Users/Oliver/Desktop/MyBookshelf setenv <strong>IPHONEOS_DEPLOYMENT_TARGET 2.0</strong> setenv MACOSX_DEPLOYMENT_TARGET 10.5 setenv PATH "/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin:/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin" /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc-4.0 -arch armv6 -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.0.sdk -L/Users/Oliver/Desktop/MyBookshelf/build/Debug-iphoneos -F/Users/Oliver/Desktop/MyBookshelf/build/Debug-iphoneos -filelist /Users/Oliver/Desktop/MyBookshelf/build/MyBookshelf.build/Debug-iphoneos/MyBookshelf.build/Objects-normal/armv6/MyBookshelf.LinkFileList -mmacosx-version-min=10.5 -Wl,-dead_strip -miphoneos-version-min=2.0 <strong>-framework Foundation -framework UIKit -framework CoreGraphics</strong> -o /Users/Oliver/Desktop/MyBookshelf/build/Debug-iphoneos/MyBookshelf.app/MyBookshelf
MyBookshelf+ for OS 3.0, the StoreKit framework was linked in:
Ld /Users/Oliver/Desktop/MyBookshelf/build/Debug-iphoneos/MyBookshelf+.app/MyBookshelf+ normal armv6 cd /Users/Oliver/Desktop/MyBookshelf setenv I<strong>PHONEOS_DEPLOYMENT_TARGET 3.0</strong> setenv MACOSX_DEPLOYMENT_TARGET 10.5 setenv PATH "/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin:/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin" /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc-4.2 -arch armv6 -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk -L/Users/Oliver/Desktop/MyBookshelf/build/Debug-iphoneos -F/Users/Oliver/Desktop/MyBookshelf/build/Debug-iphoneos -filelist /Users/Oliver/Desktop/MyBookshelf/build/MyBookshelf.build/Debug-iphoneos/MyBookshelf+.build/Objects-normal/armv6/MyBookshelf+.LinkFileList -mmacosx-version-min=10.5 -Wl,-dead_strip -miphoneos-version-min=3.0 <strong>-framework Foundation -framework UIKit -framework CoreGraphics -framework StoreKit</strong> -o /Users/Oliver/Desktop/MyBookshelf/build/Debug-iphoneos/MyBookshelf+.app/MyBookshelf+
So you can see that it is possible to build for different OS versions from the same project. Even using additional frameworks that are only available on the higher version platform don’t pose much of a problem.
Developers worldwide are looking for the “holy grail” of making a single app that runs on every iPhone OS and magically makes stuff appear that can only be done with OS 3.0. Personally I have some doubts if this is at all possible because of the extra linking that is going on between the app and the framework libraries.
Apple would have to provide libraries that could be dynamically linked to and loaded at runtime, but still even dynamic linking would have to take place during the build and this would fail when building for pre-3.0 targets. And even if you could do that I doubt that Apple would let such an app pass because they are very strict about using “private methods” that are “undocumented”.
So the approach I am presenting here (two distinct apps) might be the only possible solution for this dilemma unless Apple back-ports the new frameworks to OS 2.x.