As far as Apple goes, OS 2.x doesn’t exist anymore. That much was clear from WWDC when we asked their engineers any questions about it. And as cool as 3.0 is, with all the new nifty features, the reality is that there’s still a good percentage of (mostly iPod Touch) users out there still on 2.2. We can have our cake and eat it too by targeting 2.x and still using a few select 3.0 features. But it’s more complicated than Apple made it out to be. Trouble is looming just under the surface.
Trouble With Versions
When you install the 3.0 SDK and create a new project, it will be automatically set up to build only for 3.0. To target earlier versions while still having access to 3.0 features, you need to take a few extra step. These steps are described in detail in the readme of the MailComposer sample (iPhone dev account required). My friend Serban also wrote about how to do it when you add Snow Leopard to the mix.
The required steps are:
- Under project properties, set up the Base SDK setting to be OS 3.0
- Also in project properties, change the iPhone OS Deployment Target to OS 2.2 (or whichever version you want to target).
- Go to target properties, and add a 3.0 library with the features you need. Make sure you set its type to Weak instead of Required.
- In your code, check that a feature is available before using it. For example, you can do this check to see if the in-app mail functionality is available:
Class mailClass = (NSClassFromString(@"MFMailComposeViewController")); return (mailClass != nil && [mailClass canSendMail]);
- Make sure you set your app to build with the 3.0 SDK and off you go.
If all goes well, it should run under 2.x and 3.0. If all goes well…
Trouble With Libraries
If that was the end of the story, then we would all be happy and I wouldn’t have to write this entry. And for a while I really thought that was everything I had to do to get Flower Garden to use 3.0 features and still work on 2.x devices. Everything compiled fine, but when I went to run it on a 2.2 device, it crashed.
Looking at the crash logs, it was crashing inside a static library that used Objective C and UIKit. Digging further, it seemed that function calls were being sent to the wrong place. What was going on?
At this point I realized the root of this problem was the linker flags I was using. As soon as I started using the 3.0 SDK, I had to add the -all_load linker flag in order to be able to use the static library. I believe this loads all the symbols used by the libraries and links with them at link time. Without it, the library code would crash at runtime as soon as it was executed
The -all_load flag seems fine, except that the 2.x and 3.x versions of the SDK have different libraries and resolve symbols to different locations. So by doing -all_load, we’re linking against the location of the 3.0 version and trying to run it on 2.x. Bad idea.
I thought long and hard on how to get around this. I came up with all sorts of crazy schemes, and after a couple frustrating days, I gave up. Then, all of a sudden, I realized that it had an embarrassingly simple solution: Don’t use a library! I’m not kidding. Just move the files in XCode directly into your main game target and you’re done. No -all_load and everything works fine.
Yes, I’m still embarrassed for not figuring that out after 30 seconds…
Trouble With Compilers
So all happy with that discovery, I rebuild and run the app and… crash again!
The call stack this time looked like this:
Thread 0 Crashed: 0  dyld                            0x2fe01060 dyld_fatal_error + 0 1  dyld                            0x2fe07ca8 dyld::bindLazySymbol(mach_header const*, unsigned long*) + 484 2  dyld                            0x2fe15eb4 stub_binding_helper_interface + 12
What was going on in there? Some Googling and searching in the iPhone forum later, I learned that SDK 3.0 uses a different version of GCC (4.2 instead of 4.0). That means it will try to use some runtime functions that are not available with earlier versions. In particular, my crash was related to subtracting two uint64_t variables. Re-writing the code by casting the values to uint32_t before doing the operation fixed the problem. There’s an ugly “solution” for you!
So how do you know if something will work on 2.0? I don’t have a good answer for that other than test it as much as you can. Does someone have a better solution?
The good news is that, after I made those fixes, Flower Garden was happily running on 2.2 and 3.0. Now I can finally roll out in-app email without giving up on 2.x devices and cutting my potential customer base (or depriving current users of future updates).
Open Questions
Going through this answered a few questions, but also created a few new ones. Maybe someone here will know the answer or will be able to point me in the right direction.
- Does anyone know how to debug your OS 3.0 app on the simulator set to 2.2? Whenever I launch it from the debugger, the simulator gets set to 3.0. Even if I set it to 2.2, I wonder if it will behave the same as a 2.2 device.
- I’ve heard rumours about somehow, packing two versions of the app in the same executable (kind of like the universal MacOS executables with both a PowerPC and an Intel version). Has anyone done something like that with the iPhone? Any docs on that?
Have you tried launching the app in the 2.2 simulator then connecting to the running instance with the debugger? Just go to Run -> Attach to Process in XCode. If your app doesn’t show in the list, find the PID with ps or Activity Monitor and pick the Process ID… option. XCode will attach (and pause) the process, allowing you to set any breakpoints you need and then continue.
I did try attaching the debugger, but the app didn’t show up in the list, like you mentioned. I’ll try finding the PID and attaching directly and see if that works. Thanks.
Guess what, I forgot to put the weak link flag for 3.0 frameworks. And shipped the app. My 2.1 users started shouting as the app would crash just after launching. It was a disastrous situation.
I follow your steps but the button send do nothing in 2.2.1 is like doesn’t have the method to show the ComposerEmail. but in 3.0 works fine.
any help
thanks
- (void)email
{
//Can send email
//if ([MFMailComposeViewController canSendMail])
Class mailClass = (NSClassFromString(@"MFMailComposeViewController"));
if (mailClass != nil && [mailClass canSendMail])
{
// the custom icon button was clicked, handle it here
MFMailComposeViewController *controller = [[MFMailComposeViewController alloc] init];
controller.mailComposeDelegate = self;
[controller setSubject:@"In app email..."];
[controller setMessageBody:@"...a tutorial from mobileorchard.com" isHTML:NO];
[self presentModalViewController:controller animated:YES];
[controller release];
}
/*
// Attach an image to the email
NSString *path = [[NSBundle mainBundle] pathForResource:@"Images" ofType:@"plist"];
NSData *myData = [NSData dataWithContentsOfFile:path];
[controller addAttachmentData:myData mimeType:@"image/png" fileName:@"rainy"];
*/
}
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
[self becomeFirstResponder];
[self dismissModalViewControllerAnimated:YES];
}
I ran into exactly this problem of debugging in 2.2 with the simulator. As mentioned above, you can attach to the process (i just used ‘ps -x | grep -i appname’). i had to add a sleep when my app initialises as the crash was coming within the first second. hardly an ideal solution, but hey.
turns out my problem was i subclassed UIButton and while it all works fine under 3.0, 2.x didn’t like it one bit. as soon as the derived button was added to a view it would crash. (no doubt i will now discover that it is well known one shouldn’t subclass UIButton 🙂 ).
@edgard – you can’t launch the mail app in the simulator (it doesn’t exist), so the mailcomposer example will only work in 2.x on a device.