Lights Off

There was a time when I would have never considered jailbreaking my iPhone. That was a time before I saw Lucas Newman’s and Adam Betts’ groundbreaking application for the iPhone: Lights Off.

It’s a simple game. It’s simple code. And it demonstrated what was possible for the rest of us outside of Cupertino. I was hooked. Big time. Seeing Lights Off at C4[1] was an inspiration for pretty much everything I’ve done on the iPhone since.

As a result, I feel compelled to document this historic piece of software. And what better way to do this than with source code that works on the iPhone 2.0 software. The archive also includes a Jailbreak folder that contains the original source code that worked with version 1.0 of the iPhone OS.

Do not look at this code for tips on how to design iPhone applications correctly. Rather, it is a testament to the curiosity and coding instincts that were required for developing sophisticated software without any documentation or headers. I purposely made as few changes as possible while porting to 2.0: FileMerge can be used to see what’s improved since Jailbreak and you’ll see plenty of compiler warnings.

Since Lights Off is inspired by Tiger Electronic’s game Lights Out, it doesn’t feel right to release this via the App Store. You will have to build and install it yourself; no exceptions. If requested, I will remove this project from the site, so get it now.

Now who will be the first to reach line 212 in puzzles.plist?

Note: If Apple feels this information is crossing over the NDA line, I’ll be removing this essay and the accompanying code. It’s probably a good idea to save it for reference. Hopefully they’ll realize that college students and other developers learning about the iPhone will find it helpful.

Nike – iPod

Having just presented a talk at C4[2] about the human factors involved in developing touch-based applications, I find it rather ironic to see the Nike + iPod integration move into the latest iPod touch.

Why? Because I see some serious problems with how our bodies will interact with this device and its software.

Note: These are first impressions. I obviously haven’t had a chance to use the product, so the implementation by Apple/Nike is just a guess at this point in time. I’ve heard that there are some special controls in this software that allow “eyes off” control: if that’s the case, then we’ll all learn something from that UI.

The first problem is the size. When I’m running, I want the smallest piece of equipment possible. The simple reason is that any mass acts as a pendulum as you move. Sure the new iPod touch is lighter than its predecessor, but it’s still bigger and heavier than its nano sibling. Bigger is certainly not better.

There’s also a problem with where this device will attach to your body. Because of the size and weight, it’s likely that you’ll need to use it on an armband or in your pocket. Both of these locations present problems with interaction. You can’t see buttons on a touch screen when they’re in your pocket. It’s also uncomfortable to operate an interface that is strapped to your arm: try to unlock, launch and use an application while it’s positioned on your upper arm. Now do it while you’re running.

Good luck finding the PowerSong button without looking, too. Unlike the Nike + iPod application on the nano, this and other operations can’t be done solely by feel. Being able to operate the device while running is essential: you literally don’t want to take your eyes off the road. This “eyes off” approach to interaction is why the new iPod touch has physical volume buttons and why it was the most popular request by customers.

Some may argue that this device will be fine in a more controlled setting such as a gym. But if you’re running on a treadmill, there is already plenty of feedback from the machine’s built-in sensors and monitors. You don’t need a sensor in your shoe.

But in either case, you’ll find the biggest interaction problem is that sweaty fingers don’t work well on a capacitance-based touch screen. The salts in your body fluids make it much harder for the device to recognize your input. If you don’t believe me, try dissolving a teaspoon of salt in a cup of water. Then dip your finger in the salty water and try using the screen. You’ll find touch controls are jerky and non-responsive. Now add some body movement and you’ve got a real interaction problem.

Sometimes a few simple buttons, and not a fancy multi-touch UI, is the best way to solve an interaction problem. 

 

Dealing with memory loss: the cleanup

As we saw in the previous post, your view controller’s view can be released at any time because the device needs memory. One of the things you’ll want to look at in your own code is how you cleanup when when the memory warning occurs.

Here’s an interface for a simple view controller class that embeds a web view. The web view is retained because our controller will need to send it messages:

@interface IFWebViewController : UIViewController
{
  UIWebView *webView;
}

// webView is retained because we'll want to do things like this:
//   [self.webView loadRequest:[NSURLRequest requestWithURL:newUrl]]
@property (nonatomic, retain) UIWebView *webView;

@end

We setup and retain the web view when the controller’s loadView method is called:

- (void)loadView
{
  ...

  UIView *contentView = [[[UIView alloc] initWithFrame:applicationFrame] autorelease];
  self.webView = [[[UIWebView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, applicationFrame.size.width, applicationFrame.size.height - 44.0f)] autorelease];
  [self.webView setScalesPageToFit:YES];
  [self.webView setDelegate:self];
  [contentView addSubview:self.webView];

  self.view = contentView;
}

Eventually this method will be called:

- (void)didReceiveMemoryWarning
{
  // default behavior is to release the view if it doesn't have a superview.

  // remember to clean up anything outside of this view's scope, such as
  // data cached in the class instance and other global data.
  [super didReceiveMemoryWarning];
}

The view and its sub-views are automatically released and set to nil by the default implementation. But there’s a problem here: the primary consumer of memory in this view has not been released. The web view is still being retained by the controller.

Since UIViewController doesn’t notify you that the view is being freed, you’re left holding onto a web view that you can’t do anything with since it’s not a part of the view hierarchy.

You can’t solve this problem by using a non-retained reference to the view because you’ll be left with stale reference and a crash if you try to use it. Instead, you’ll want to override the method used to set the view in UIViewController:

- (void)releaseSubviews
{
  self.webView = nil;
}

- (void)setView:(UIView *)view
{
  if (view == nil)
  {
    NSLog(@"setView: releasing subviews");
    [self releaseSubviews];
  }
  [super setView:view];
}

Of course, you’ll also want to release the retained views when the controller is freed:

- (void)dealloc
{
  [self releaseSubviews];
  [super dealloc];
}

When you’re debugging this code, make sure to force the memory warnings in the simulator. You can do this easily with the Hardware > Simulate Memory Warning menu item.

I’ve also found it helpful to poke around in the view controllers instance variables to see what’s allocated, check retain counts, etc. Since you can’t access self.view or [self.view superview] without instantiating a new view instance, you’ll need get at the private property using KVC:

- (void)didReceiveMemoryWarning
{
  // use this to look at view (without creating an instance with self.view)
  NSLog(@"didReceiveMemoryWarning: view = %@, superview = %@", [self valueForKey:@"_view"], [[self valueForKey:@"_view"] superview]);

  [super didReceiveMemoryWarning];
}
(Do not be an idiot and use this technique in production code: there’s nothing to prevent the iPhone SDK developers from renaming this property and causing your app to crash.)
When debugging, it’s also convenient to know when the view is instantiated (or re-instantiated.) Some logging code in viewDidLoad does the trick:
- (void)viewDidLoad
{
  NSLog(@"viewDidLoad: view = %@", self.view);
}

A lot of the complexity in this memory cleanup could be mitigated by UIViewController supporting a unloadView that is called prior to setView:nil in the default didReceiveMemoryWarning. If you agree, feel free to submit a duplicate bug on Radar ID# 5834347.

At present, there are no metrics for memory usage on the device, so there’s a bit of guesswork in determining how much of your view controller’s object graph should be released. If in doubt, free your data and let it be re-instantiated lazily. You’ll find the overall user experience to be much better.

Note: If Apple feels that sharing this information is outside of the bounds of the NDA, I’ll be forced to remove this post. So just add this to your ever growing collection of furbo PDF files.

Dealing with memory loss: the crash

Now that we’re beta testing and able to symbolicate our crash logs, let’s look into one of the crashes that they helped me solve in version 1.0 of Twitterrific.

The crash was happening during the posting of a notification. The backtrace looked something like this:

Program received signal:  “EXC_BAD_ACCESS”.
#0	0x300c87ec in objc_msgSend
#1	0x30675b0e in _nsnote_callback
#2	0x3025380c in _CFXNotificationPostNotification
#3	0x30673f46 in -[NSNotificationCenter postNotificationName:object:userInfo:]
#4	0x3067aa00 in -[NSNotificationCenter postNotificationName:object:]
#5	0x000029fe in -[TwitterrificTouchAppDelegate startTweetRefreshWithMessagesAndFavorites:] at TwitterrificTouchAppDelegate.m:183

The method that initiated the crash was sending IFStartTweetsNotification. This notification allows several views in the UI to reconfigure themselves while the application is refreshing.

One of those views is the toolpad that you see at the bottom of the detailed tweet view. It is a subclass of UIView and is instantiated like this:

- (id)initWithFrame:(CGRect)frame;
{
  self = [super initWithFrame:frame];
  if (self)
  {
    ...
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshStarted) name:IFStartTweetsNotification object:nil];
  }
}

When the view was being freed, this code was being used:

- (void)dealloc
{
  ...
  [super dealloc];
}

I had looked at all of my code and was so convinced that this was a UIKit problem that I filed a Radar. But it turns out that it really was my fault: I had overlooked this little bit of code that gets defined when you create a new view controller:

- (void)didReceiveMemoryWarning {
 [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
 // Release anything that's not essential, such as cached data
}

Simple enough to understand: whenever the system determines that it is running low on memory, it starts to call these methods. The default implementation is to free a view if it’s not a part of the current view hierarchy.

With a desktop application, views are never released without an explicit action. (And it’s particularly hard to free them if they are instantiated via a NIB.) As a result, some of them us have gotten a bit sloppy with our view cleanup code. And that sloppiness is what bit me.

When my toolpad was out of view, as it is when you are looking at the main list, and a memory warning was issued, the detail view was released. Because the toolpad was a subview of that view, it was also released.

So what happens when you send a notification to an object that no longer exists? That backtrace I showed you above.

The fix was painfully easy:

- (void)dealloc
{
  [[NSNotificationCenter defaultCenter] removeObserver:self name:IFStartTweetsNotification object:nil];
  ...
  [super dealloc];
}

The lesson to be learned here: if you’re doing any kind of registration or allocation in your init methods, make sure that you have corresponding action in your dealloc method. Even if you don’t really need them on the desktop.

In my next essay, I’ll show you how to clean up memory when getting one of these warnings. It’s not as straightforward as you might think.

Note: If Apple feels that sharing this information is outside of the bounds of the NDA, I’ll be forced to remove this post. If you want, pretend like it’s 1995 and print this out instead of bookmarking it.

Symbolicatifination

Now that we’re all beta testing, we’ll hopefully get some crash logs from testers. But you’ll quickly realize that these crash logs don’t look as good as they do when you pull them off the device with Xcode’s Organizer: there are no symbols and fricken’ useless because you can’t tell where the code is crashing.

Thanks to the free-flowing information regarding this wonderful SDK we’re all using, it took me awhile to figure out how Xcode “symbolicates” these crash logs. The secret is here:

/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Plug-ins/iPhoneRemoteDevice.xcodeplugin/Contents/Resources/symbolicatecrash

I copied this script into my ~/bin folder so I have easy access to from the command line. You’ll probably want to put it somewhere in your $PATH, too. I chose not to use a symbolic link because I don’t want to lose this valuable script during an Xcode upgrade.

After you’ve gotten things setup how you’d like, it’s a simple matter of running the script like this:

% symbolicatecrash Twitterrific_2008-07-21-191314_Nero.crash

Note that the script uses Spotlight to locate the .dSYM files used by atos. (Check the top of the PERL source code for a summary of how this tool works.)

This has a very important implication for your iPhone app development. It is absolutely essential that you save your .dSYM files for any release you make public. You also need to make sure that Spotlight is able to index those copies. (Some of us like to keep Spotlight out of project folders.)

I’d also suggest taking a look at the source code for the script. You’ll see that architectures besides “armv6” are supported: if you have a need to symbolicate logs from “i386”, “x86_64”, “ppc” or “ppc64”, it should work fine. There are also some options for parsing the crash log that I don’t fully understand. Yet.

Again, if Apple feels this information is crossing over the NDA line, I’ll be removing this essay. It’s probably a good idea to save it for reference. In any case, enjoy your newfound ability to symbolicate!

Update August 14th, 2008: symbolicatecrash has a bug that prevents it from working correctly when there is a space name in the path where the .dSYM files are being archived. Your app symbols won’t be included in the output if they are stored in a folder like “~/Projects/dSYM Archive”: use “~/Projects/dSYM_Archive” instead. (rdar://problem/6150458).

Update August 14th, 2008: If you add this script to the end of your build, it will automate the process of creating the .dSYM and .app archive. You’ll need to update the configurations which will be checked: $CONFIGURATION = “Distribution-Free” is specific to my project. Note that the .app binary that’s saved is not the same one that ends up in your build folder. It hasn’t been compressed or code signed (rdar://problem/6150088), but it appears to have enough information for symbols to be resolved with atos.

Update September 25th, 2008: If you’re having problems locating the crash logs, take a look at this article. It explains where iTunes puts the files on Mac OS X, Windows XP and Vista.

Update December 11th, 2008: Bryan Henry has discovered and fixed a bug with symbol name lookups. If you’re experiencing this problem, duping the Radar wouldn’t be a bad idea either.