Future Proofing your Application

Session 130 WWDC 2010

iPhone OS has a rich set of APIs that enable you to build great applications. However, applications that make incorrect assumptions may fail to run properly on future versions of iPhone OS. Understand the most common mistakes and how to achieve your application's goals and maintain compatibility.

Hi everyone.

I'm Henry Mason.

I'm an Application Engineer on the Application and Frameworks iOS Group at Apple.

And today we're going talk about future proofing your applications, which is a pretty simple concept really.

We want applications written today to run on devices that run iOS in the future.

So it means devices like iPad and iPhone and iPod touch, we want your apps to run on future versions of those and any other devices we have running iOS and we want your applications to run on versions we haven't announced yet.

We work really hard to do this and we know you really want that to happen and we really want it to happen so we want a little bit of your help to make sure this continues to happen in the future.

So what we're briefly going to go over is some concrete examples of problems we've seen which have caused compatibility problems.

So this means things that application developers have done that have caused problems in future versions of iOS.

We're going to go over some examples of how to fix those and what you can do to prevent those kind of problems.

Then we're going to go over a few tools and techniques we have to sort of find problems before they happen.

So just to get this out of the way real quick, one of the major and highly preventable problems that can cause compatibility problems is use of non-public API.

And just to be very simple about this, if you don't find a class method, function, symbol, in a header or a documentation, you really shouldn't use it.

It's generally not documented or not in a header, not because we're trying to be malicious or we don't like you but because it's not ready for your use yet.

If you find something you need and it's not in a header or documentation, the correct solution is to file an enhancement request.

Just let us know you need this feature because it's not there yet and we'll see what we can do about adding that as soon as possible.

Keep in mind we do, you know when we get duplicate requests, usually these are what we've already heard about a problem, something isn't available, we consider the number of dupes in scheduling fixes.

So please do let us know when you have a new use case that we probably haven't thought of yet and then we can get that maybe added as public API.

And of course even if something is in a header or is documented there are ways to misuse API, you're going to cause problems.

So here's an example we've seen in a few applications, we want to get a view controller and put it on to a navigation controller and then at the end of it, after the animation is done, we want to well do something, you know have an alert, take off a task, whatever.

Well one thing you might realize is that the animation on one version of iOS always takes the same amount of time.

And this is some very kind of clever reverse engineering to discover that it takes .4 seconds to do this animation but this isn't future proof because it is entirely possible that we'll change that duration in the future.

And even if we didn't change that duration, performs select their effort delay isn't guaranteed to be precisely timed so this could end up sort of breaking the sync of your application.

And in almost all cases where we've seen this, there are real public ways of doing it.

In this case you can just use the navigation controller, delegate callback, to know when the view controller is done being pushed and then you can do your work.

And this will work much nicer and we'll be able to change the duration of the animation in the future and maybe even the style of the animation and your app will actually require less code.

Another example of this is sort of going through our view hierarchy.

We've seen a few cases where we have system classes, in this case a UI video editor controller, and some applications have needed some part of that system view.

I've sort of changed the names to protect the innocent here but this isn't really going to work.

The hierarchy of our views and our view controllers is very much subject to change.

The way we lay out our views and even the way they look are almost certain to change between iOS releases.

In particular, the ordering of subviews is not defined.

And of course the contents of the layers of these views is also not really well defined.

In the vast majority of cases that have done this, there are now lower level API available to get the information you're looking for.

In particular, if you really just want to grab an image off the video capture device, you're going to look at the new AV foundation, AVCaptureDevice class, new in iOS 4.

So another technique in Objective C is Categories, which is a really powerful technique for adding functionality to system classes and sort of organizing your classes into multiple segments so you can have, you know, one part of your class in one file, one part in another file.

It really isn't a great way for over-riding the behavior of system classes, however.

Although the runtime and the linker will let you do it, it's kind of fraught with peril.

For example, if you really want all of your views to be green, it isn't the right technique to just over-ride DrawRect for all your views and make the background green.

Although this might work in one version, the implementation of DrawRect isn't stable.

You know we could be changing that in the future.

So you don't want to do this.

The correct solution is to you know make your own view or even just use some other public API we have available.

So you make your own subclass, you know insert that where you need it and this will prevent you from kind of stomping on other code like system classes that depend on behavior or will in the future.

Another little problem with categories is that it's actually very easy to accidentally over-ride a method for a class that doesn't exist, for a method that doesn't exist yet.

It sort of sounds weird but let me give you a hypothetical here, let's say for a convenience you want to have a getter for the height of a view so you add height to UIView, well that's okay.

The problem is it's entirely possible that UIView in the future will actually have a height property and it might be defined differently than what your app considers height.

And then all of our system frameworks that are built on having the height property behave some way are not going to work.

So you have to kind of use your imagination here but the basic point is that any system class with a common name is very likely that, you have to assume that we're going to add some method with that name in the future.

So a really easy work around for this is to sort of name prefix methods that you add to system classes.

So if your app is called my app, just sort of add my app to the beginning of the method name you add to system classes and then if we add a height property in the future, you won't sort of stomp on the system implementation.

And this isn't a purely hypothetical example, by the way.

Back in the early 2000's when there were dinosaurs, there was also use hidden property on NSView back on the desktop, on app kit so a number of applications actually broke when they were adding their own use hidden property to views and when they would run on the new version, the OS that had hidden property built into NSView, everything went crazy.

So you don't want to do that.

Something else that's caused some compatibility problems, you know crashing of applications when run on new OS versions, is sort of not correctly handling some of the more surprising API we have in the system.

Exceptions by and large are designed to catch programmer error.

You shouldn't really rely on exceptions being catchable and your application in its normal operation should never throw an exception.

Well that seems good.

The problem is that there are some API that actually do throw exceptions under sort of surprising circumstances and this is one of those.

So there's a very subtle bug here but let's say this method has a user input string so it's some sort of untrusted input from the user types and we convert it to a URL, we get the host out of it, we make a mutable string, we mess with it, we do with it whatever.

The tricky bit here is that NSURL actually will return nil if the input is not valid, not a valid URL, which normally in most of our API is okay.

We're pretty safe about dealing with nil.

The trick is that if you then send that nil object of URL a host message and then here you pass that nil objects immutable string with string, that's actually going to crash.

It's going to throw an exception and it is documented but it's kind of, this can cause some compatibility problems.

If in one version URL is slightly more strict about handle how it parcels URL's or if the user input is somehow changing so it isn't URL anymore, this is going crash on one version and work fine on another version of iOS.

So there's a couple of things you can do here, one is basically just check to see that API UCall doesn't throw exceptions or you handle those cases.

Another is to use slightly more safe non-exception throwing API, in this case you can use mutable copy on NSString which is okay with a nil receiver.

Something else I'm sure a lot of people have noticed is that we don't really have name spaces so we have to deal with naming conventions for all of our system classes, which basically means that system classes and functions have a two letter prefix like UI or NS or CA or whatever.

You can't really use those.

Those are considered system private.

Don't use two letter prefixes for your classes, much better to just sort of create your own prefix and use that.

Event handling in UIKit is another sort of area of framework gotchas.

There's some sort of surprising behaviors at the way events work in UIKit that have caused problems with backwards compatibility.

The biggest one is the way some people like to forward UITouches.

What this means is you get a touch in one view and then you send it off to another view, you know for whatever reason.

This is technically supported but it is fraught with peril.

Every responder that gets the UITouch must be your own custom subclass of UIView or there's going to be crazy, undefined behavior.

And as a result this also means that you can't insert any of our system classes or system views into your view hierarchy and have it work correctly.

You can't use some of the more nice touch handling controls that we've been adding recently.

So again, it's supported but highly discouraged.

And I'll show you there's a much better solution for that in iOS 3.2 and later.

It's also important to remember to implement touchesCancelled.

We've seen a lot of cases of people overriding touches began, moved, ended and forgetting to do cancelled, which leaves their application in some weird state.

And it's entirely possible through a reasonably rigorous testing run through through your application, to not catch this but it really can leave your application in a weird place and especially now that we're getting more like Local Notifications and Push Notifications, there's going to be more and more cases where your applications will have to handle touches getting cancelled because of an alert popping up or something.

So it's really critical you implement and work with touches getting cancelled.

And on a related note, just going back to UITouch forwarding, in the vast majority of cases where people were doing it, UI Adjuster Recognizer is a much better solution.

It's new in 3.2 and will solve a lot of your problems.

Another little UIKit surprising problem is the way UITableViewCells are views which necessarily means they have subviews but you shouldn't be adding views directly to UITableViewCells.

If you want to add something to a TableViewCell, like a custom view, you actually want to add it to the content view and this will allow you to get the highlighting to work correctly and our layout will work better.

So again we can't really catch people actually doing this but it will cause compatibility problems and has actually caused a number of applications to have weird behaviors in their table views when upgrading to iOS 4.

So another problem, it's actually sort of a more general purpose problem, is accidental blocking of your UI.

The main thread in iOS applications is primarily designed for servicing your user's requests, responding to touches and motion events from the user and interpreting that sort of thing.

It isn't designed for, you shouldn't be doing long-running computation or network access or file reading on your main thread.

And this is actually something of a forward compatibility problem because something may very well be fast in one release of the OS but then get slower later.

And in this example we're doing a loading of a URL into a TableViewCell.

The problem here is, although the NSString, string with contents of URL, may very well be fast in one version of the OS.

You know let's say that the URL is a file URL or something like that and it's very quick to load, it's entirely possible that in a future version of the OS, that will get slow.

Maybe that URL will turn into a web URL or something and it has to make some long running request to you know the internet, the cloud.

So you don't want to do this kind of thing.

You want to use either non-blocking API or move your long running computation and network access and file access to a background thread.

So this leads to another common mistake which can cause compatibility problems.

All right you see here, well I don't want to do this in the main thread so let me do it on a background thread.

Okay well I can use performSelectorInBackground and now my work runs in the background thread and doesn't block the UI and my table scrolls nicely and my image on the TableViewCell just kind of pops in when it gets loaded and no big deal.

The problem is there's another common gotcha which is that UIKit classes really should only be used from the main thread, with a few very specific exceptions.

In particular imageNamed, accessing the image view of a TableViewCell and changing the image of an imageView, none of that can be done from a background thread.

It may very well work on one release but then cause crashes later and you really don't want that.

So to solve this you have this sort of three step process.

First you kick off your work to a background thread.

You do the background safe work, in this case loading the content of the string content of URL on the background.

And then you message back to the main thread to do the UIKit stuff there.

So we're doing the imageNamed loading and the imageView manipulation back on the main thread, which will be nice and safe for your user and for your app.

iOS 4 actually makes it a bit easier.

You can use libdispatch or the MS operation QAPI and the new block syntax to handle this nicely.

So this is an equivalent thing that requires less code and gives you a bit better compile time with error checking.

So you just sort of create a block that does the string contents loading and then another block that does the work on the image view and the UIImage on the main thread and you sort of dispatch those to the queues you need and requires a little bit less code and does basically the same thing.

Another case where, another thing that's new in iOS 4, which kind of goes back to the idea of not using your main thread to do long running computations, is your applicationDidEnterBackground method.

When you adopt fast app switching, your application delegate will receive applicationDidEnterBackground when the user switches out of your app.

This might seem like a good time to sort of do some cleanup and it usually is.

The problem is you actually only have about 10 seconds in iOS 4 to respond to this.

If you don't return from that quickly enough, your application will be forcibly terminated and you'll effectively be opting out of fast app switching.

So if this dual out of work with object does a lot of work with this object that takes a minute, your app is effectively not working with fast app switching when you implement it this way.

So you can do a similar thing we were doing with the TableViewCell in the DidEnterBackground, except when you're editing the background you need to first request a background task from the application and then you can start doing your work.

So here we request a background task from the application.

If we get it we do the lots of work on a background global concurrent queue that won't block the main thread and then we terminate the task and then the app can go to sleep and the system, your app will be able to quickly resume when the user goes back to your application.

Another cool thing in iOS 4 is that there's a few more thread safe API, UIGraphics and part of UIImage and of course the background task identifier stuff is now accessible off the main thread.

So there's a few new UIKit things you're now allowed to do in the background, which is great for not blocking your main thread and not causing your UI to lock up and also great for, you know, future compatibility if we get a little bit more strict about how long you have to block the main thread before we kill you.

So let's look at a few kind of nasty assumptions that have caused some kind of embarrassing problems on applications between OS releases.

You really should never be hard coating pixel or point values of our widgets in your applications and views.

The font metrics and the margins of our views and labels and stuff aren't really stable.

It's possible those can change.

You know we might make slight pixel changes.

The way our buttons and table U cells look are sort of subject to change.

We might make those a little tighter, a little smaller, a little bigger so you don't want to just, you know, sort of reverse engineer again that oh well it's always 10 pixels here so I'll just hard code that I'm going to move my drawing over by 10 pixels.

We provide API like in the string editions, in UIKit to determine how big strings should be and you really want to use those.

And it isn't just good for compatibility of the future versions of the OS.

It's also really good for internationalization.

When you want to start supporting languages beyond English or whatever languages you are on now, you really need to be able to handle longer and shorter human readable strings in your applications.

So you know kind of getting started on that early and making sure the UI is kind of flexible to handle that sort of thing, is a really good idea.

And of course we are no longer in a 320 by 480 world.

You know just in the last few months we've gone from 320 by 480 to iPad size devices and now the retina display on iPhone 4 and who knows where we're going from there.

[laughter] So really do make use of the UIScreen bounds that we provide.

You know if you want to make your thing full screen, we provide it.

We provide a way to get that.

Just say, you know get the main screen or whatever screen you're drawing on, if you're using TV out, get the bounds of it and that gives you the points you want to use to lay out your views, which is a really, really good idea.

Another case we saw with the introduction of the iPad was that we actually had very good luck with the application compatibility in compatibility mode.

So the vast majority of iPhone applications work very nicely when we're in an iPad incompatibility mode but a couple of them, we launched them and nothing happened.

And in a huge proportion of these cases we ended up finding code in these applications that look something like this.

You should really never be using the model property of the UIDevice object for almost anything.

It exists sort of just a purely informative thing to determine, you know maybe the name of the device to show the user like you know thank you for running my application on your iPhone.

You should never use it to determine how your UI looks.

So there's two main problems with this code here, the first is it makes the unfortunate assumption that iPhones and iPod touches, you know having that model name determines what capabilities you have, which is actually already a problem because you can see that even since last year iPod touches have had microphone in accessories so if your application requires a microphone, camera, SMS, you don't want to be using a device name to determine that.

And of course the bigger problem is, if device model returns something other than an iPhone or iPod touch, your application will set up no user interface, which is what we saw on all these iPad examples.

No UI got set up at all.

So what you do want to use are the capabilities based API provide.

If you want to know if a microphone is available, AVAudioSession can tell you.

If you want to know if there's a camera, the ImagePicker can tell you and if you want to know if you can send SMS, you can ask the application if it can open an SMS URL.

There's a few other examples in the documentation but the critical thing is use specific API for the capability you need and secondly, if the capability isn't available but you still want to support that device, just set it up unconditionally, not based on the model name.

So basically, always make sure to set up something.

And of course there's a few new capabilities in iOS 4 and in iPhone 4 and we have new API for that so I want to show you a couple of those.

So one is the new message framework stuff lets you compose SMS's, text messages so there's a cool new class method called canSendText, which lets you know if your device has been set up for sending text messages.

The event kit system has a new support for calendar and date based stuff and you can see what events are supported in a given calendar.

The gyroscope in iPhone 4 is exposed through core motion.

And this also works for using the excelerometer, there's new stuff in core motion for that.

So just check to see if the gyro is available on the CMMotionManager and that will work nicely.

If you want to know if you have a retinal display, you can check the scale property of a screen.

In iPhone 4 the scale is 2, for all the other devices it's 1.

But you should handle any scale.

And of course the new cameras, there's great availability stuff in the image controller for the new camera capabilities to determine if there's a front or back camera, how you can determine which camera has a flash, if any and you can determine which camera has a video or still capabilities.

I'm going to switch gears a little bit and talk about some of the tools we have available to let you sort of catch problems before they happen.

And the biggest tool is just the new versions of the SDK.

As soon as new betas become available, it is absolutely critical that everyone download them, install them, recompile your application with the new SDK and make sure it work.

If there's any unexpected behavior change, well first check to see if there's a bug in your code that was exposed by this but if you can't find a bug in your code, we really want to hear about it, even if you're not sure, please file a bug and say my app used to do this, now it does this, what gives?

We really, really want to hear that.

And in particular, turn on deprecation warnings.

Just go into your project target settings and check the warn about deprecated functions checkbox and it will tell you about any API that we aren't supporting anymore.

Deprecated means we're not adding features to it, we're not fixing bugs in it and there's a better replacement available.

So in this example on the screen we've got the final attributes of path traverse link method on File Manager, which has been deprecated for a little while.

You definitely don't want to use that anymore and the header can explain some reasons why.

So why is it important that you link against new versions of the SDK?

Why not just take the binary you had, you know your old version of your application, put it on you know the new beta OS and see how it works?

The answer is we employ a technique we call linked on or after.

Every once in a while we do find bugs in our system frameworks.

And by and large we try to fix those bugs but if we have third party applications that could potentially be inadvertently depending on those bugs, we have to be very careful about how we fix them.

And our technique is that if there's any chance that fixing this bug will cause applications to crash, we try to use what's called a linked on or after check, which is to say we check to see what version of the SDK was used to compile the application.

And if it was a version from before the bug was discovered and fixed, we maintain the old buggy behavior.

So let me give you a little example of this, I'm not sure if anyone noticed but up until 2.0 or up until 3.0, in iPhone OS 2, imageNamed actually leaked.

It returned images that were a little bit over retained, just by 1 but that was enough.

[laughter] You shouldn't, you know, rely on this particular example of retain count but this just shows the problem.

So image named, it should be returning something with retained count of effectively 1 but it was actually returning one with 2, obviously auto released but well when we discovered this problem, we didn't want to just start returning a retained count of you know a correctly retained object because applications could have been inadvertently depending on the application being over retained.

So even on future versions of iOS, as long as the app was compiled with an older SDK, we'll still return an over retained object.

But as soon as you compile with the new SDK's, we'll return a correctly retained object.

So if any application was inadvertently depending on that behavior, they'll start to crash.

And we employ this by and large pretty much anywhere we can.

It will cause sort of behavior changes and accidental memory corruption and stuff like that.

So it is really, really critical to compile with new versions of the SDK when they become available.

And another great way to find bugs before they sort of become problems is the static analyzer.

It really is just about the best thing ever.

It catches a lot of errors and the cool thing about it is that it can actually catch errors on code paths that you don't test in your normal testing because for whatever reason your application doesn't go down that code path yet.

It truly can like walk through functions and methods and branches and all that stuff and find over-retains and uses of uninitialized variables and I can't say enough great things about it.

One really cool new thing in recent versions of Xcode 3 and later, is that you can now turn the Static Analyzer on immediately after, by default so that every time you build your application, a Static Analyzer runs.

I would highly advise doing that.

It can catch bugs before they become problems and will really, really make development much easier and much nicer.

There is one small problem with the Static Analyzer, which is that because it's built on the new LLVM compiler, anything it can't compile won't get analyzed and will just come back with zero bugs, which means you might not be getting as much coverage of your code as you think.

So that also means that one thing we can't solve just yet is coverage of C++, which actually the Static Analyzer can't quite cover yet anyway.

But also there are some C and Objective C idioms we've seen in code, including ours, which GCC accepts but the new compiler won't like, won't deal with.

So as a result, one way to get better coverage of the Static Analyzer is to switch to the LLVM compiler.

You don't necessarily have to switch to it permanently, if you're not quite ready to use the cutting edge compiler system but just maybe once in a while switch to the LLVM compiler.

Make sure your app compiles cleanly with it and then the Static Analyzer will give you much better results.

There's some great new features in the iPhone Simulator in iOS 4.

The biggest is that we've bought over the new run time.

It has a lot of cool new features but the best part of it is you can now run newly, you know, iOS 4 compiled applications on older versions of the SDK and we're going to support this going forward.

Currently we only support as far back as 3.2 but even with just that, that's a pretty great thing because that means for universal app development you can compile with the iOS 4 SDK and simulate it on the iPhone OS 3.2 system.

So since some of the new stuff in iOS 4 isn't quite available in the iPad yet, iPad's are still going to run 3.2 for a while, this is a really, really convenient thing.

And again, we're going to continue to support this in the future.

And obviously it also works the other way.

You can compile something for 3.2 and then run it on the 4.0 SDK, the Simulator.

The Simulator is also really great to support sort of unusual configurations that you might not, you know, consider testing on your devices.

You know test to make sure your UI is good with the in-call status bar.

You know the double height status bar which is going to become, you know, more and more apps are going to use that sort of thing in the future.

Low memory warnings, in an era of multitasking when you've got lots of applications on your system, it's entirely possible that you're going to get more, you're more likely to get memory warnings today than you used to be.

And applications are getting bigger and all that stuff.

So make sure to test low memory warnings, which the simulator can do very easily.

You just go into menu, go to simulate low memory warning and make sure your application works correctly.

Just do that every once in a while.

But of course the simulator is still not a replacement for the device.

If you plan to ship on, given on OS version it is critical that you actually test on that OS hardware version on a device.

In particular, the performance characteristics are different and we don't support some of the new background modes like background location, navigation and void stuff and the background audio playback.

That stuff isn't correctly stimulated so stick with, you know, make sure to use the device when you can.

Instruments is great for catching sort of runtime problems.

It's a great idea to run leaks in the allocations instruments every once in a while, just you know maybe once a week or something.

And if you notice your application is leaking memory, what we have typically found is that, well first of all that's a very bad thing.

You want to fix that.

But you could have been accidentally, you know, relying on that leak to cover up some other over retain or over release, which is going to cause crashing later on.

So every once, you know run leaks, fix your leaks, make sure you're not abandoning memory and then make sure your app doesn't crash after you fix those leaks.

If you have a leak and you fix it and your app starts crashing, it is not the correct thing to put the leak back in.

[laughter] That means you have a problem you need to fix.

One of the really great things in iOS 4 SDK is the automation instrument, which lets you sort of automatedly test your user interface, you know, and drilling down through the UI without manually tapping things.

I would make use of that.

It's really great for finding, you know, behavioral differences between OS versions.

Sort of set up a good test suite of automated tests.

Run it on different versions.

Make sure everything works correctly.

So we're going to kind of switch gears again for a second and talk about sort of past proofing your app.

A lot of people want to ship their application such that it takes advantage of new features on new operating systems but at the same time can still run on old operating systems that don't support those features, which is noble.

It's a good thing to do.

But you have to do a little bit of work to make sure that works correctly.

If you want to call a new C function or use a new C symbol, if you compile against it with the correct minimal version set but using the new SDK, when you run on older devices, that symbol will just be null.

So calling it is going to crash.

So for example if you want to use the new UI Accessibility VoiceOverRunning C Function and you still want your app, which is new in iOS 4, but you still want your app to run on old versions, you need to sort of gracefully fall back by checking to see if the function is available and not null and if it is available, you can go ahead and call it, otherwise sort of fall back to some reasonable behavior.

So just assuming that it's not running is probably reasonable here.

So that's a nice way of doing that.

There's a new method that's available on Existing Class that's not available on older versions of the OS.

You can use RespondsToSelector, which is a class instance method on NSObject.

So for example if you want to see if the beginBackgroundTask with identifier API is available.

You take an application, probably your application, the shared application in your process, you ask if it responds to that method, that selector and if it does you can use the, you can call it and use that result.

On old devices you'll just use the background test unifier invalid, which is also what happens on older devices running iOS 4.

So for example, on the original iPhone 3G you'll get an invalid background task identifier anyway so you're going to handle that regardless but this will work nicely even on older operation system versions.

If you want to use a new class, you can use NS Class from String.

You pass the name of the class you want to use, in this case let's say you want to use UI NIB for the cool new NIB loading stuff we have in iOS 4.

That will return an object which is a class, in Objective C classes are objects.

If the class is available you can use it.

If not, you need to create some kind of fall back behavior.

You can use maybe the NS bundles loading stuff, NIB loading code.

So there is one thing we don't quite support yet.

Let's say you want to subclass a new class, like let's say you want to subclass UIGestureRecognizer and have it run on old versions of the OS.

This is technically feasible with our runtime so that the framework is there but our compiler and linker don't actually support it just yet.

So if you do have a real need for this, please file an enhancement request and we'll see what we can do.

So just to go over it one more time, finding compatibility is not an easy problem but we are committed to it and we want your apps to work on future versions of the OS as much as you do and your customers do.

So be vigilant about compiling with new versions of the SDK, finding bugs, file those bugs and let us know about it and there's really great tools available to, well catch these problems before they become real problems.

For more information you can talk to Bill.

Definitely, as always, read the documentation and check out the Dev Forums, in particular the Coding Guidelines goes over some of the stuff about how we handle future, you know future methods and stuff like that in our frameworks and the Dev Forum's always great for answering questions you have about you know what might work in different versions of the OS.

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US