I was quite looking forward to getting a chance to improve my already quite satisfying Mac experience to the latest iteration of the operating system. Having bought my new MacBook Pro after Juni, I was elegible for an extra cheap upgrade. So I filled in my serial number and got promised a delivery about a week afterwards. But not being the patient type I figured it would not be a sin to borrow a friend’s DVD and install the new cat. Hey, I own a license, who cares which media I’m using?
Installation was as painless as can be. You pop in the DVD while your Mac is running, click a couple of times and then you wait. After 20% there is a first reboot, then you wait, all together roughly 40 minutes. At first you don’t see any new stuff, except the same welcome Animation playing in a Window instead of full screen. But then you start to see little things. Many little things, in fact enough to warrant a 23-page in- depth technical review on Ars Technica which is great bedside reading making you dream nicely about your invigorated Mac.
I was excited to install the new Xcode 3.2 from the “Optional Installs” folder on the DVD, then I downloaded and installed the latest iPhone 3.1 SDK for Snow Leopard which in contrast to Leopard is just the SDKs and now Xcode. The Leopard version contains Xcode 3.1.4, which for strange reasons does kind of run, but is “not officially supported by Apple to run on Snow Leopard”. Still, I will show you how you CAN compile against a 2.x SDK. After that I will show you Apple really wants you to do, but leaves it up to SDK diggers to tell the public.
Fixing Deprecated Code
One cockup on Apple’s behalf was to not care for 2.x compatibility of the Simulator. Not only do they not include the SDKs for 2.0 through 2.2, but also you have to compile against SDK 3.x if you want to test in Simulator. That’s a major problem if you use code that is deprecated as of 3.0, like in my case a couple of the tableview properties.
For example, you can no longer use cell.image to set the image, but you have to use cell.imageView.image instead. I did not want to force all users of MyAppSales onto a 3.x SDK just yet, so I put in a workaround using some #defines.
Put these defines into your PCH file:
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 30000 #define CELL_LABEL textLabel.text #define CELL_IMAGE imageView.image #define TABLEVIEWCELL_PLAIN_INIT [super initWithStyle:UITableViewStylePlain reuseIdentifier:reuseIdentifier] #else #define CELL_LABEL text #define CELL_IMAGE image #define TABLEVIEWCELL_PLAIN_INIT [super initWithFrame:frame reuseIdentifier:reuseIdentifier] #endif |
And to use the do a mass-replace over all your project so that they are used like this in the init of a custom UITableView cell:
- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier { if (self = TABLEVIEWCELL_PLAIN_INIT) { // Initialization code // you can do this here specifically or at the table level for all cells self.accessoryType = UITableViewCellAccessoryNone; self.selectionStyle = UITableViewCellSelectionStyleNone; // create label views to contain the various pieces of text that make up the cell. } return self; } |
And also to set the label text and image, do this:
cell.CELL_IMAGE = someImage; cell.CELL_LABEL = @"someText"; |
With these modifications I was able to run the project in Simulator 3.0. When choosing any previous version my app starts displaying the splash screen, but then it crashes with this message on the console:
dyld: Symbol not found: _CFXMLNodeGetInfoPtr Referenced from: /System/Library/Frameworks/Security.framework/Versions/A/Security Expected in: /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator3.1.sdk/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation in /System/Library/Frameworks/Security.framework/Versions/A/Security |
I getting many reports and questions what to do about this and the official response from Apple has been that it’s simply not supported. And since they don’t comment on work in progress, not even to developers, you don’t know if this will work eventually.
How to build against 2.x SDKs – A Workaround
Poking around the /Developer subdirectories I found two places where I could restore the SDK subdirectories pre-2.2.1 from Timemachine. This would get the SDKs to show up in Xcode. To be able to compile it some more trickery was necessary, because Snow Leopard is called Darwin10 internally. I found this article showing the necessary soft links.
The third and final piece of the puzzle is a new deployment target build setting. No longer does the chosen SDK dictate the minimum OS version to be runnable on. This setting gets written into the bundle’s info.plist. This is also added if you build against a from-Timemachine-restored SDK. Using all these techniques combined I was able to Build&Debug MyAppSales for platform 2.0 and run it on the device.
But the official statement: Don’t.
I consider especially worthy of your attention the new explanatory text for each build setting. For the deployment target it says “Framework APIs that are unavailable in earlier versions will be weak-linked; your code should check for null function pointers or specific system versions before calling newer APIs”.
And that’s the officially recommended approach. If you still are using pre-3.0 deprecated functions you better replace them with their modern counterparts. To continue to support customers and be able to test the code on Simulator you have to BUILD against a 3.x SDK but set the deployment target to something lower.
This actually answers a second question of ours, how it can be possible to support 3.0 features in apps that have a minimum requirement of 2.0 on the app store. You just detect if its possible to instantiate the necessary classes and if you cannot then you don’t present the feature. For example you try to instantiate the in-app mail viewcontroller and if it does not exist you have to be smart about the nil value that you get returned.
The major killer feature of Xcode 3.2 for me is to be found somewhere else …
Static Analysis
Xcode 3.2 introduces “static analysis” which does not only tell you regular syntax errors, but also does an intelligent analysis showing you SEMANTIC errors as well. Like if you alloc-init something but forget to release it, it will tell you. This hides in the build menu.
Be prepared for dozens of Aha! moments when you start this. Even well polished code like MyAppSales (one may dream!) might contain memory leaks, strange coding practices and sometimes overreleasing of objects. At the time of this writing I had to switch to Simulator for the Analyze errors to show up, don’t know why it did not work for a Device build.
It’s amazing how intelligent this works. It spots leaks with a surefire accuracy. Like in this an actual example from the MyAppSales 1.6 codebase. There are even arrows and annotations that explain the “reasoning”. In this case I am alloc-initting a dictionary, but I don’t release this owning reference at the end of the method. Therefore it leaks, because the retain count stays at 1 but no pointer refers to it anymore. As a matter of personal price I went through about 45 such problems and eliminated all crucial ones for the next release.
With these modifications I was able to build a 2.0 release bundle, and at first glance you might assume that all is well, who needs to run 2.x code anyway on Simulator.
I spent all the day fixing leaks with the analyzer, with a lot of “Aha!” moments š ! It’s a really usefull tool!
And the Ars Technica post is a must read for all developers.