views:

59

answers:

1

A user has sent in a crash report with the stack trace listed below (I have not been able to reproduce the crash myself, but every other crash this user has reported has been a valid bug, even when I couldn't reproduce the effect). The application is a reference-counted Objective-C/Cocoa app.

If I am interpreting it correctly, the crash is caused by attempting to send a drawerDidOpen: message to a deallocated object. The only object that should be receiving drawerDidOpen: is the drawer's delegate object (nowhere does any object register to receive drawer notifications), and the drawer's delegate object is instantiated via the XIB/NIB file, wired to the delegate outlet of the drawer, and not referenced anywhere else.

Given that, how can I protect against the delegate getting dealloc'd before the drawer notification? Or, alternately, what have I misinterpreted that might be causing the crash?

Crash log/stack trace:

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000010
Crashed Thread:  0  Dispatch queue: com.apple.main-thread

Application Specific Information:
objc_msgSend() selector name: drawerDidOpen:

Thread 0 Crashed:  Dispatch queue: com.apple.main-thread
0   libobjc.A.dylib                 0x00007fff8272011c objc_msgSend + 40
1   com.apple.Foundation            0x00007fff87d0786e _nsnote_callback + 167
2   com.apple.CoreFoundation        0x00007fff831bcaea __CFXNotificationPost + 954
3   com.apple.CoreFoundation        0x00007fff831a9098 _CFXNotificationPostNotification + 200
4   com.apple.Foundation            0x00007fff87cfe7d8 -[NSNotificationCenter postNotificationName:object:userInfo:] + 101
5   com.apple.AppKit                0x00007fff8512e944 _NSDrawerObserverCallBack + 840
6   com.apple.CoreFoundation        0x00007fff831d40d7 __CFRunLoopDoObservers + 519
7   com.apple.CoreFoundation        0x00007fff831af8c4 CFRunLoopRunSpecific + 548
8   com.apple.HIToolbox             0x00007fff839b8ada RunCurrentEventLoopInMode + 333
9   com.apple.HIToolbox             0x00007fff839b883d ReceiveNextEventCommon + 148
10  com.apple.HIToolbox             0x00007fff839b8798 BlockUntilNextEventMatchingListInMode + 59
11  com.apple.AppKit                0x00007fff84de8a2a _DPSNextEvent + 708
12  com.apple.AppKit                0x00007fff84de8379 -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 155
13  com.apple.AppKit                0x00007fff84dae05b -[NSApplication run] + 395
14  com.apple.AppKit                0x00007fff84da6d7c NSApplicationMain + 364
15  (my app's identifier)           0x0000000100001188 start + 52

edit: To clarify: This crash happened once in thousands or maybe tens of thousands of virtually-identical usage scenarios. I don't retain/release/alloc/dealloc/anything-memory-management the delegate object anywhere in my code; I don't register any object for any drawer notifications of any kind in my code; my code has no variables (nor ivars) pointing to the delegate object.

What it looks like to me is that when the NIB was unloaded (as in whatever the Cocoa system does when the document window gets closed), somehow the drawer's delegate object was dealloc'd before the drawer object itself, but the Cocoa system is supposed to prevent that from happening (and seems to handle it correctly in the vast majority of cases).

A: 

The delegate has been released to the point of deallocation without first being unregistered from the notification center (in this case, the delegation is through the notification center, not directly).

An easy fix is to call NSNotificationCenter's -removeObserver: method from your delegate's -dealloc method.


Check the backtrace -- it is through the notification center that the crash happens. Most likely, when the class is connected via setDelegate: (via the NIB file), it is done so as a notification observer.

Regardless, the relationship between notification center and your object is the same as between the object in IB and your object; weak. That is, there is no retain and, thus, your delegate is being freed too early (or, alternatively, you are over-releasing the object somewhere).

In any case, you need to make sure your delegate is retained by something somewhere for the duration of its usefulness.

bbum
If the delegate were registering itself via `NSNotificationCenter`, I'd understand (and in fact, I twice checked for any instances of NSNotificationCenter registering drawer notification), but the delegate is only instantiated in the NIB and only referenced by the delegate `IBOutlet` on the `NSDrawer`--that is, the only way to get a pointer to the delegate object in my code as is would be via `[theDrawerObjectOfSomeWindow delegate]`.
Isaac
Regarding the backtrace/notification center, yes, it is going through the notification center, but since I didn't ask to observe, I should not have to remove the observer (i.e., if registering is hidden from me, deregistering should be hidden, too).Per http://developer.apple.com/mac/library/documentation/cocoa/conceptual/LoadingResources/CocoaNibs/CocoaNibs.html#//apple_ref/doc/uid/10000051i-CH4-SW6 top-level objects instantiated in the NIB have a retain count of 1 in a reference-counted environment, so I don't see why/how the delegate object is getting dealloc'd.
Isaac
Oddly, while the cited docs say that I should release top-level objects instantiated in the NIB, "Build and Analyze" flags the release as a bug.
Isaac
Try calling `setDelegate:nil` in your delegate's `release` (maybe you are already).If you're loading the NIB via a window or view controller, those classes will handle the top-level releases for you (IIRC).
Wevah
Yes-- I should have mentioned that 'setDelegate:nil' effectively removes the observer in some delegate cases
bbum
Okay, so it's probably the document/window controller (it's a document NIB) that's releasing the top-level objects and for some reason in some edge case, the delegate was released before the drawer. I'd gotten to thinking I could probably guard against this crash with `setDelegate:nil`, but it hadn't fallen into place for me that in the delegate object's `dealloc` method I should add `if ([theDrawer delegate] == self) [theDrawer setDelegate:nil];`. Without being able to reliably reproduce the problem, I can't be sure this fixes it, but it seems like it should.
Isaac