MKMapView, Storyboard, and ARC… or rather the lack of it, and the crash I encountered

hsoi blog, talk 3 Comments

I just fixed a troubling and sporadic crashing bug, and thought I’d share in case it helps someone else.

I’m working on an iOS app, writing in Xcode 4.3 against the iOS 5 SDK. This app is using Storyboard for the GUI, thus segues are in play. That means I don’t have explicit control over the lifetime of certain objects, like the ViewControllers. This project is also using Automatic Reference Counting (ARC), so that also means I don’t quite have explicit control over the lifetime of objects.

I have a HomeViewController, which is the app’s initial screen. Via a UINavigationController, you can tap on a button in the HomeView’s toolbar and it will segue (all Storyboard, no code involved) to a MapViewController (class names changed from the actual project names to facilitate explanation). As you might guess, the MapViewController hosts an MKMapView. Furthermore, the MapViewController acts as the MKMapView’s delegate. That’s the key bit of information.

What I saw was the app launched, I tap the button to segue to the MapView. Map does its thing, drawing MKCircle’s and MKPointAnnotations, and doing other things that a good MKMapViewDelegate would do. But then from time to time when I tap the navigation back button, the app would crash. The resulting output wasn’t very useful, but it was evident some deallocated object was being messaged. The trouble was figuring out what object.

I won’t bore you with the details of my sleuthing around, but after a few hours of investigation, enabling zombies, lots of debugging by NSLog(), I figured out the problem.

The MapView’s delegate went stale.

It seems that there was a timing issue. The app segued back to the HomeViewController, -[MapViewController dealloc] was invoked, but the MapView was still processing things. So the map went to invoke the delegate, which was now stale, and things went boom.

Since the delegate was declared as “assign”, the reference wasn’t automatically zeroed. And so, we have to.

Added a simple thing:

- (void)viewWillDisappear:(BOOL)animated
{
    self.mapView.delegate = nil;
    [super viewWillDisappear:animated];
}

And life is good.

Not necessarily surprising, in terms of how delegates work (and have always worked). But with all the advances of Storyboard, ARC, and so on, you just get locked into that paradigm of things getting done for you. It’s simplified the coding process, but we still have to be vigilant. Furthermore, I didn’t quite think the map view would still be sending around data after it’s “parent”, the MapViewController, was certainly dealloced. That’s what is more curious to me: why were the delegate messages still being sent after the MapViewController — which is the parent/owner of the MKMapView — still being sent? Maybe something internal still held a reference to the MKMapView instance I guess. Bowels of the OS, I just can’t know.

Comments 3

  1. Man oh man, thanks for posting this. I think together we spent probably about 10 to 12 hours trying to track down the cause of this crash. I learned way more about debugging than I think I ever wanted to know, although it was ultimately tracing it to commenting out the map view’s delegate assignment that led me to this blog post.

    I’m going to dump in some of the terms I was using to track down this issue in the hopes that Google will make it easy for people to find this post in the future:

    zombie mkmapview delegate
    zombie mkannotation
    zombie uisplitviewcontroller uitabbarcontroller map

  2. Post
    Author

Leave a Reply