1

I'm occasionally getting this crash, which is very hard to reproduce:

0   CoreFoundation                  0x39ff73e2 __exceptionPreprocess + 158
1   libobjc.A.dylib                 0x3905095e objc_exception_throw + 26
2   CoreFoundation                  0x39ffaf2c -[NSObject(NSObject) doesNotRecognizeSelector:] + 180
3   CoreFoundation                  0x39ff9648 ___forwarding___ + 388
4   CoreFoundation                  0x39f51204 _CF_forwarding_prep_0 + 20
5   Foundation                      0x32914bec -[NSDictionary(NSKeyValueCoding) valueForKey:] + 28
6   MyAppName                       0x00032112 -[myViewController stopPlayersWithVolume:] (myViewController.m:151)

From this code:

- (void)stopPlayersWithVolume:(double)volume
{
    for (NSString *file in players)
    {
        AVAudioPlayer *player = [players valueForKey:file];
        if (player.volume == volume || volume == 0)
            [player stop];
    }
}

players is an NSMutableDictionary property, accessed without self. because I don't believe it's needed with ARC. The keys are filenames (e.g. "mysound.mp3") and the values are AVAudioPlayer objects.

Is the stack trace saying that the parameter file I'm passing to [NSMutableDictionary valueForKey] is not actually an NSString, and hence the unrecognised selector? It is always an NSString in the debugger, as you'd expect as it comes from a fast enumeration through the dictionary, but the crash never occurs in the debugger.

My only thought is that there's a threading issue corrupting the dictionary. The code above is being fired by an NSTimer but that's just a message via the run loop, not a separate thread, so I believe there should be no need to worry about cross-thread access?

Any suggestions appreciated. Sorry to just dump this out but I'm stuck.

Edit:

The players dictionary is allocated in viewDidLoad thus:

players = [[NSMutableDictionary alloc] init];

And declared as follows:

@property (retain, nonatomic) NSMutableDictionary *players;

And synthesised as follows:

@synthesize players;
Nestor
  • 2,753
  • 2
  • 29
  • 34
  • 2
    What is the key? `valueForKey` does some m@gic that `objectForKey` does not. – Thilo Dec 14 '12 at 12:25
  • It looks to me that although the players object is defined as an NSMutableDictionary it is pointing to an object of another class (maybe it is as an NSArray?). – diederikh Dec 14 '12 at 12:26
  • Could you copy and paste the literal error message? There might be some clue in the details. (Break on Objective-C exceptions if it's not currently logging an exception message.) – Phillip Mills Dec 14 '12 at 12:26
  • I only have the crash dump from Organizer, do you want that? There's no exception message in there, just `Exception Type: EXC_CRASH (SIGABRT) Exception Codes: 0x0000000000000000, 0x0000000000000000` – Nestor Dec 14 '12 at 12:28
  • @Thilo, I've edited the first paragraph to explain what the keys are, is that what you meant? – Nestor Dec 14 '12 at 12:34
  • Can you show the lines where you assign the players property? – diederikh Dec 14 '12 at 12:36
  • How about wrapping the lines inside your `for` loop in a @try/@catch section and putting `NSLog(@"%@: %@", [exception description], file);` in the @catch part. The result should show on the device console even if you don't get an error while debugging. – Phillip Mills Dec 14 '12 at 12:45
  • @DiederikHoogenboom, have edited to include the players property as per req. – Nestor Dec 14 '12 at 12:47
  • How do you fill the dict? Doesn't it need to be `AVAudioPlayer *player = [players objectForKey:file];` instead of `valueForKey`? – ott-- Dec 14 '12 at 13:46
  • @ott, it was indeed the filling/allocation of the dictionary, details such as they are in my answer below. Thanks. – Nestor Dec 14 '12 at 14:54

2 Answers2

1

It's likely that your players dictionary or its contents are being deallocated while you're iterating through them. Can you turn on NSZombies and try to reproduce the crash in the debugger? It will give you more information about what object is being deallocated.

Community
  • 1
  • 1
Jesse Rusak
  • 56,530
  • 12
  • 101
  • 102
  • And one little style thing: with a dictionary, you probably want to use ```objectForKey:```, not ```valueForKey:```, though this is probably not the problem. – Jesse Rusak Dec 14 '12 at 12:37
  • Yes I've done a lot of profiling with NSZombies enabled but nothing came up. I allocate the dictionary like this: `players = [[NSMutableDictionary alloc] init];` which I momentarily thought was the problem as I missed `self.`, but you don't need it with ARC to get the retain, do you? – Nestor Dec 14 '12 at 12:39
  • Agreed, I should have used `objectForKey:`. Have learned lots about NSDictionary today... – Nestor Dec 14 '12 at 12:41
  • If this is pre-iOS 6, try running in the simulator and causing memory warnings. Is it possible your timer keeps running after you leave this view controller, for example? – Jesse Rusak Dec 14 '12 at 12:44
0

The code to allocate the dictionary (and set up the audio stuff) looked like this:

NSError *error = [[NSError alloc] init];
NSArray *names = [fm contentsOfDirectoryAtPath:resourcePath error:&error];
players = [[NSMutableDictionary alloc] init];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:&error];
[[AVAudioSession sharedInstance] setActive:YES error:&error];

I must have copied and pasted it from somewhere, because that use of NSError is wrong, as far as I know, though hardly likely to cause a problem. I changed the code to this:

NSArray *names = [fm contentsOfDirectoryAtPath:resourcePath error:nil];
players = [[NSMutableDictionary alloc] init];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];

And the problem goes away, it appears. Can't be 100% sure but I can reproduce the crash within 10 attempts with the former code and never with the latter. No idea why.

Nestor
  • 2,753
  • 2
  • 29
  • 34
  • 1
    If you want to use the error output of the method, you just declare the variable: NSError *error; You don't alloc init it. The method itself, creates the error object, it just needs a place to put it. – rdelmar Dec 14 '12 at 17:03
  • The app's in production now and never crashes. So it appears that using NSError wrongly as above causes crashing. – Nestor Dec 30 '12 at 16:46