2/15/2009

Differences Between the Simulator and Device

If you've done a bit of iPhone development, you've probably run into scenarios where the simulator and your device behave differently. Generally speaking, it's because the simulator uses OSX's libray (I think) and is a bit more "friendly" to errors, whereas the iPhone is missing a couple things here and there and is much more picky about errors. Case in point - comparing strings. With the simulator, you'll find that the following statement probably works fine:

if (myTestString == @"Blah blah blah") dosomethingcool;

However, the device will balk and possibly crash. What you're supposed to do, and the thing that works on the iPhone, is:

if ([myTestString isEqualToString:@"Blah blah blah"]) dosomethingcool;

In another example, you'll see my entry below about NSUserDefaults. My pro-tip there is that loading an NSMutableArray results in an immutable (uneditable) array, but only on the device. In the simulator, I was able to make changes and everything worked OK (it seemed anyway). But on the device things were effed. So the lesson is: test on your device early and often.

As a final note, and something that I'm beginning to find very useful for a number of reasons, is a little app called AppKiDo. Basically, it lets you quickly browse through the different foundations and included methods and check out their properties, methods and so on and so forth. So, you can look up NSString and easily see what sorts of things you can do with it, or look up AVAudioPlayer and see what its properties are. You can get all this from the docs, but AppKiDo makes it way easier and faster to do so, in my opinion. And since you can have it work specifically for the iPhone, you can more easily find out if a piece of code that you found online is actually supported or not.

Save Your App's State (and Settings)

The iPhone (and I suppose Cocoa as well) makes it very easy to save and retrieve data. Most commonly, this will be settings of some sort, but you could save the state of your app so that when someone starts it again, it picks up from the same place, or something similar.

What you want to use is NSUserDefaults. Using it is extremely simple. First, just declare your NSUserDefaults pointer.

NSUserDefaults *myDefaultOptions = [NSUserDefaults standardUserDefaults];

To save an object (or variable), do something like the following:

[myDefaultOptions setObject:myTestString forKey:@"firstPlayerName"];

In this case, "firstPlayerName" is the name of the key that I'm saving the object to. It can match your in-use variable name if you want (which I often do). You might want to make a save method that gets called with a button press and writes out a bunch of objects at one. When you want to then to load the object again:

myTestString = [myDefaultOptions stringForKey:@"firstPlayerName"];

Basically, that's it, but you'll need to run a test on your loads so that when someone runs the program for the first time (or hasn't saved), then you'll take care of it. In the case of a string, if there isn't a corresponding NSUserDefaults entry for what you try to load, it comes up as NULL. So, after loading the save, you could run a quick check like the following:

if ([myTestString == NULL]) myTestString = @"Blah blah blah";

PRO-TIP: You can save arrays to NSUserDefaults and retrieve them easily, but there's a catch - even if you save out an NSMutableArray (one that's editable - NSArray is not), it will come back as immutable (uneditable). I ran into some crazy issues because of this, and it wound up being a pain to track down until I figured it out. It's actually an easy fix though - just add "mutableCopy" to the end of your read. So, it would look something like:

myArray = [[defaultOptions objectForKey:@"mySavedArray"] mutableCopy];