iPhone: debugging EXC_BAD_ACCESS errors

After developing for iPhone and Android for quite some time now I consider myself an advanced developer on both these mobile platforms. But even though I left the beginner stage, sometimes things still don't work as they should (or at least as I expected) and I thought sharing some of my learning experiences would help other developers to lessen their pain.

One of the biggest pains (besides Objective-C itself) is definitely the memory management on the iPhone. Luckily we don't have this problems on Android, due to the fact of using Java and garbage collection, which makes programming an ease (almost!) and enables you to focus on creating something rather than wasting a lot of time debugging. However, Apple decided not to include garbage collection for the iPhone, even though it's available within their frameworks and is actually used for Mac development. I won't elaborate much more on the pros and cons of Objective-C's memory management and the object ownership concept, since a lot of other bloggers ranted about that topic already too many times before me, but rather give you some useful tips to reduce the time you spend debugging.

The most common problem you will have is probably an EXC_BAD_ACCESS error, which occurs when you try to release memory that has already been released. This could be the reason if for example you try
  • to release an object, which you don't own
  • to release an object, which is in the autorelease pool
  • to release an object, which has the "assign" property instead of "retain"
The problem with that is, that the error doesn't occur at the point where you accidentally release the object, but when the object gets released somewhere else at a later point in the code, where it is actually supposed to be released. This could be either by another owner or when the autorelease pool is getting cleaned-up. And this "somewhere else" might not be easy to find. In general I recommend to compile and test your code as often as possible, because of the complex way you have to declare variables and allocate memory in Objective-C (declare, @property, @synthesize, eventually retain and release) it's very likely that you forget one of the steps in your code, especially when things need to be done quickly. In the end you will spend more time debugging and finding the problem than what it would have taken with just being more thorough from the beginning on.

You might, for example, get an output like this:
2010-03-07 15:03:11.136 MyProject[58016:207] *** -[UIView setFrame:]: message sent to deallocated instance 0x1b33240
Program received signal:  “EXC_BAD_ACCESS”.
Finding the source of that problem is hard by just knowing the object's address in memory: 0x1b33240. Thus we should enable a few options in our executable's settings: under group & files doubleclick on your executable and then under the "Arguments" tab add and enable the following options:
NSDebugEnabled
MallocStackLogging
NSZombieEnabled
MallocStackLoggingNoCompact


Now recompile your code, run it in the simulator and try to reproduce the same error you had before. But this time you will be able to enter the following command in gdb:
shell malloc_history $PID $ADDRESS_OF_OBJECT_IN_MEMORY
In our case that would be (see the pid and address I marked red above):
shell malloc_history 58016 0x1b33240
and results with the following (or similar) output:
ALLOC 0x1b33240-0x1b33267 [size=40]: thread_a02fa500 |start | main | UIApplicationMain | -[UIApplication _run] | CFRunLoopRunInMode | CFRunLoopRunSpecific | PurpleEventCallback | _UIApplicationHandleEvent | -[UIApplication sendEvent:] | -[UIApplication handleEvent:withNewEvent:] | -[UIApplication _reportAppLaunchFinished] | CA::Transaction::commit() | CA::Context::commit_transaction(CA::Transaction*) | CALayerLayoutIfNeeded | -[CALayer layoutSublayers] | -[UILayoutContainerView layoutSubviews] | -[UINavigationController _startDeferredTransitionIfNeeded] | -[UINavigationController _startTransition:fromViewController:toViewController:] | -[UINavigationController _layoutViewController:] | -[UINavigationController _computeAndApplyScrollContentInsetDeltaForViewController:] | -[UIViewController contentScrollView] | -[UIViewController view] | -[CategorySuperViewController loadView] | -[CategoryViewController initWithStyle:] | -[Functionality requestNewAd] | +[AdMobView requestAdWithDelegate:] | +[NSObject alloc] | +[NSObject allocWithZone:] | _internal_class_createInstance | _internal_class_createInstanceFromZone | calloc | malloc_zone_calloc
What we can see is where exactly the error occurs, but we still don't know by what it is caused and where this happens in the code. What we know though, is that it has something to do with an AdMobView object, which we most likely released at some other point in our code where we shouldn't have released it (because we didn't have ownership or because it's on autorelease).

To fix the problem we just check all AdMobView releases. If you're unsure you can just temporarily disable them, recompile and run it again to see if that was the problem. Another thing, that happens to me sometimes, is that I accidentally type dealloc instead of release, which immediately removes the object from memory, while with a release it might keep existing, because it may still be owned by someone else. In general, never use dealloc (unless you really know what you're doing).

I hope this helps you to make iPhone development and debugging a bit easier and please let me know if something is still unclear.



I've just launched a new side project of mine:

Bugfender - A modern remote logger tailor-made for mobile development

It's currently free and you can sign up here: https://app.bugfender.com/signup

Any kind of feedback is more than appreciated :-)

1 comment:

  1. Thanks you for this sentence: "Another thing, that happens to me sometimes, is that I accidentally type dealloc instead of release". Thanks a million times over. Thanks[0], Thanks[1], ... Thanks[999999]

    ReplyDelete