Overview
I'm seeing a strange issue in a Mac OS X Cocoa app I'm developing w/Xcode 4.3.2 and testing on Mac OS X 10.7.5 (targeting Mac OS X 10.6): I have a basic NSMatrix radio group in my main NIB that has one outlet and one action in my main controller, but is also used in two other methods (also in the main controller). The action saves the selected tag to NSUserDefaults & enables/disables an NSTextField based on which is selected, one method is a preferences loader and so selects a cell by tag upon load, and the other method looks up it's selected tag for other logic (code samples below).
Basic stuff, but in one case (when only having selected a cell in the NSMatrix programmatically, not via clicking a radio button in the GUI), the app PWODs in a mutex lock.
The Code & Functionality
I've renamed variables & methods below, but have not changed any of the other structure of the code. passwordRadioGroup
is the NSMatrix*
IBOutlet.
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification {
// get logging up and running
[self initLogging];
// load various prefs
// ...
[self loadPasswordSettings];
}
- (void)loadPasswordSettings {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// load password settings from our prefs
NSNumber *passwordTypeTag = [defaults objectForKey:kPasswordTypeDefaultKey];
if ( passwordTypeTag != nil ) {
[passwordRadioGroup selectCellWithTag:[passwordTypeTag intValue]];
} else {
[passwordRadioGroup selectCellWithTag:kPasswordTypeRadioRandomTag];
}
[self selectPasswordType:passwordRadioGroup];
// ...
}
- (IBAction)selectPasswordType:(NSMatrix *)sender {
NSInteger tag = [[sender selectedCell] tag];
switch ( tag ) {
case kPasswordTypeRadioRandomTag:
// disable the password text field
[passwordField setEnabled:NO];
break;
case kPasswordTypeRadioManualTag:
// enable the password text field
[passwordField setEnabled:YES];
break;
}
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInt:tag] forKey:kPasswordTypeDefaultKey];
}
- (NSString *)generateUserPassword {
NSString *password;
// which type of password should we be generating (random or from a specifed one)?
NSInteger tag = [[passwordRadioGroup selectedCell] tag];
switch ( tag ) {
// manual password
case kPasswordTypeRadioManualTag:
password = [passwordField stringValue];
break;
// random password
case kPasswordTypeRadioRandomTag:
// password = randomly generated password;
break;
}
return password;
}
When the app is launched, my main controller is sent a applicationDidFinishLaunching
message which then sends the loadPasswordSettings
message to itself. loadPasswordSettings
then sends [[passwordRadioGroup selectedCell] tag]
and sends [passwordRadioGroup selectCellWithTag:]
. This works correctly and the correct radio button is selected in the GUI. It finally calls [self selectPasswordType:passwordRadioGroup
(which also successfully calls [[passwordRadioGroup selectedCell] tag]
) and appropriately enables/disables the text field and writes the tag back out to NSUserDefaults (usually, but not always redundant).
You can select any of the radio buttons, which sends a selectPasswordType:
message to my main controller (passing it the instance of passwordRadioGroup
; I've verified the memory address in the debugger and can inspect its ivars). This successfully calls [[passwordRadioGroup selectedCell] tag]
and saves the tag to NSUserDefaults.
You can do the above two as many times as you like without issue. Quitting & relaunching correctly restores the radio buttons to the state you left them last, so it's definitely correctly getting the selected tag, storing it in NSUser defaults, retrieving it from NSUserDefaults, and setting the selected cell by tag on the NSMatrix.
Here's where it gets screwy:
There's another button that, when clicked, does a bunch of other work and ultimately then sends the generateUserPassword
message to itself to generate a password (again, this is all in the main controller and running in the main thread). What's the first thing that it does? Calls [[passwordRadioGroup selectedCell] tag]
. Fine, you can safely do that as much as you want, as illustrated above, right?
If you select one of the radio buttons, therefore changing the selected cell via the GUI and sending the selectPasswordType:
message to my main controller again, yes. You will encounter no issues (although, I admit it seems a bit slow and PWODs for a second).
If you do not click on the NSMatrix after launch (so not forcing the selection/action again from the GUI), that [[passwordRadioGroup selectedCell] tag]
call in generateUserPassword
will immediately PWOD. If I hit the pause button in Xcode's debugger to see where it's at, it's always in psych_mutexwait
(called from class_lookupMethodAndLoadCache
) in the main thread. If selectPasswordType:
weren't called programmatically and able to run [[passwordRadioGroup selectedCell] tag]
without issue, I'd have at least some sanity left.
Help!
To reiterate, I've followed this through in the debugger and can verify that the memory address & ivars confirm that passwordRadioGroup
is not being changed out from under me, nor is it being deallocated (I've tried with & without "Zombie Objects" enabled). The only references to passwordRadioGroup
in my main controller are those seen above. Googling for all sorts of NSMatrix/radio/selectCellWithTag
/selectedCell
/selectedTag
/class_lookupMethodAndLoadCache
/mutex terms/combinations has not been fruitful.
Any solutions, troubleshooting suggestions, or thoughts would be greatly appreciated. Slaps for stupidity also welcome, if deserved.