Unrecognised selector crash within [NSDictionary valueForKey:] - iphone

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;

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.

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.

Related

Why does clearing NSUserDefaults cause EXC_CRASH later when creating a UIWebView?

Before I begin, I should tell you that this only happens in iOS 5.1. Before the most recent update, this had never happened and it still does not happen on any other version. That said, here's what's going on.
When a user logs out of my app, one of the things that happens is that all of the NSUserDefaults get deleted. Rather than manually removing every key I might add to the user's defaults, I just completely delete all of the NSUserDefaults, using the method suggested in this SO question:
NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];
What seems to happen though, is that every time I try to create a UIWebView after having removed NSUserDefaults, I get an EXC_CRASH (SIGABRT). The crash happens when I call [[UIWebView alloc] initWithFrame:frame]. Strange right? Completely quitting and reopening the app allows UIWebViews to be created again.
So, I managed to figure out that removing the defaults would cause the UIWebView issue, but to be sure, I added a symbolic breakpoint for -[NSUserDefaults setObject:forKey:].
Creating a UIWebView does indeed trigger the breakpoint.
Poking through the crash logs gives me the exception reason:
-[__NSCFDictionary setObject:forKey:]: attempt to insert nil value (key: WebKitLocalStorageDatabasePathPreferenceKey)
And here's the beginning of the stack trace:
0 CoreFoundation 0x3340688f __exceptionPreprocess + 163
1 libobjc.A.dylib 0x37bd4259 objc_exception_throw + 33
2 CoreFoundation 0x33406789 +[NSException raise:format:] + 1
3 CoreFoundation 0x334067ab +[NSException raise:format:] + 35
4 CoreFoundation 0x3337368b -[__NSCFDictionary setObject:forKey:] + 235
5 WebKit 0x3541e043 -[WebPreferences _setStringValue:forKey:] + 151
6 UIKit 0x32841f8f -[UIWebView _webViewCommonInit:] + 1547
7 UIKit 0x328418d7 -[UIWebView initWithFrame:] + 75
8 MyApp 0x0007576f + 0
9 UIKit 0x326d4dbf -[UIViewController view] + 51
10 UIKit 0x327347e5 -[UITabBarController transitionFromViewController:toViewController:transition:shouldSetSelected:] + 93
11 UIKit 0x32734783 -[UITabBarController transitionFromViewController:toViewController:] + 31
12 UIKit 0x327340bd -[UITabBarController _setSelectedViewController:] + 301
13 UIKit 0x327bd5d9 -[UITabBarController _tabBarItemClicked:] + 345
What I'm doing for now, and what works, is just keeping track of the NSUserDefaults keys I have set and removing them all manually when I need to. But there's always a risk I might forget a sensitive key, so simply clearing all NSUserDefaults strikes me as more sensible. So, I would like to know why I can't do that. Is it a bug or am I doing something wrong?
If you want any more info, just let me know! Thanks.
EDIT: Doing [[NSUserDefaults standardUserDefaults] synchronize] after deleting all NSUserDefaults does not help.
I'm seeing this issue as well, I think you have to have created a UIWebView first before clearing user defaults for it to occur. A clean project with the following will cause the crash in iOS5.1, this works fine in iOS5.0 and earlier:
UIWebView *webView = [[UIWebView alloc] init];
[webView release];
[[NSUserDefaults standardUserDefaults] setPersistentDomain:[NSDictionary dictionary] forName:[[NSBundle mainBundle] bundleIdentifier]];
UIWebView *anotherWebView = [[UIWebView alloc] init];
[anotherWebView release];
I can work around the crash by doing this instead, which avoids having to remember all your settings keys:
id workaround51Crash = [[NSUserDefaults standardUserDefaults] objectForKey:#"WebKitLocalStorageDatabasePathPreferenceKey"];
NSDictionary *emptySettings = (workaround51Crash != nil)
? [NSDictionary dictionaryWithObject:workaround51Crash forKey:#"WebKitLocalStorageDatabasePathPreferenceKey"]
: [NSDictionary dictionary];
[[NSUserDefaults standardUserDefaults] setPersistentDomain:emptySettings forName:[[NSBundle mainBundle] bundleIdentifier]];
Anyone see any issues with doing it this way?
It sounds like you're removing some sort of private preference, which is probably a new bug, either with NSUserDefaults (you shouldnt be able to remove it) or UIWebView (it should cope with a missing entry).
Have you tried the method from the other answer (setting a blank dictionary?). Does that give the same results?
How about if you get the dictionary representation of NSUserDefaults, get all the keys, and iterate through those, removing the objects? (let me know if you need a code sample for that).
It works for me
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSDictionary *userDefaultsDictionary = [userDefaults dictionaryRepresentation];
NSString *strWebDatabaseDirectory = [userDefaultsDictionary objectForKey:#"WebDatabaseDirectory"];
NSString *strWebKitLocalStorageDatabasePathPreferenceKey = [userDefaultsDictionary objectForKey:#"WebKitLocalStorageDatabasePathPreferenceKey"];
[userDefaults removePersistentDomainForName:[[NSBundle mainBundle] bundleIdentifier]];
if (strWebDatabaseDirectory) {
[userDefaults setObject:strWebDatabaseDirectory forKey:#"WebDatabaseDirectory"];}
if (strWebKitLocalStorageDatabasePathPreferenceKey) {
[userDefaults setObject:strWebKitLocalStorageDatabasePathPreferenceKey forKey:#"WebKitLocalStorageDatabasePathPreferenceKey"];}
[userDefaults synchronize];
Simpler it will be to use the code below:
[self saveValue:#"" forKey:#"WebKitLocalStorageDatabasePathPreferenceKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
It is easier.

Can I store AVAudioPlayer references in an NSMutableArray and play them at will?

I have a library of 16 short sound clips I need to be able to play in quick succession. I realized that creating and preparing AVAudioPlayer objects in real time was too much to ask of the iPhone.
So instead, during my app's initialization, I am pre-creating a series of AVAudioPlayers so that each one is basically pre-loaded with one of my 16 sounds, so they're ready to be played at any time.
The problem is, to keep this clean, I would like to store the references for these 16 AVAudioPlayers in an NSMutableArray, so I can easily get at them just by knowing their array location. However, the way I'm doing it is crashing the simulator w/no error messages in the log:
Here is how I'm currently setting up the array of AVAudioPlayer references:
// (soundPlayers is an NSMutableArray instance var)
soundPlayers = [NSMutableArray arrayWithCapacity:(NSUInteger)16];
for ( int i = 0; i < 16; i++ ) {
NSString *soundName = [NSString stringWithFormat:#"sound-%d", i];
NSString *soundPath = [[NSBundle mainBundle] pathForResource:soundName ofType:#"mp3"];
NSURL *soundFile = [[NSURL alloc] initFileURLWithPath:soundPath];
AVAudioPlayer *p = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFile error:nil];
[soundFile release];
[p prepareToPlay];
[soundPlayers addObject:(id)p];
[p release];
}
Then later I try to load, say, sound #8 and play it back:
// (soundPlayer is an AVAudioPlayer instance var)
self.soundPlayer = [soundPlayers objectAtIndex:(NSUInteger)8];
[soundPlayer play];
Any ideas? Also does anyone know if any of the slick debugging tools that come with XCode would be useful for this type of problem?
I'm just guessing here, but there might be a problem with the capacity of the array.
Try: soundPlayers = [[NSMutableArray alloc] init]; instead.
This isn't likely to be a cause of the crash, but try NSLogging self.soundPlayer before calling -play on it to make sure that its not nil. And another minor little thing: you don't need to typecast p when adding it to the array. Just call [soundPlayers addObject:p] without (id).
I'm not sure of the exact cause of your crash, just going through the code and pointing out what doesn't look right. Hope this helps!
It is most likely the case that your soundPlayers mutable array is being released at some point since you aren't retaining it. I would ensure your property for the array is set to retain and do what Macatomy previously stated:
self.soundPlayers = [[NSMutableArray alloc] init];
Or even initWithCapacity:
It looks OK to me except perhaps you shouldn't call prepareToPlay before you put them into the array.
It's probably better to do this when you actually are going to play them. The description of that method says it preloads buffers and acquires the audio hardware for playback. Doing it 16 times at once may be too much.
EDIT - you should also add error checking, your missing results that might tell you something is wrong, initWithContentsOfURL:outError: returns an error message. Take advantage of that:
NSError* error = nil;
AVAudioPlayer *p = [[AVAudioPlayer alloc] initWithContentsOfURL:soundFile error:&error];
if (error) {
NSLog(#"ERROR: %#, URL=%#", error.localizedDescription, soundFile);
}

Core Data / NSArray Causing Leaks?

I am having a specific leak that I can't seem to dig to the bottom of, cause my search always ends up in some Apple libraries. Any help from some veterans at dealing with this would be appreciated.
Here is the relevant source code: (leak indicated with a comment)
- (void)viewDidLoad {
//[super viewDidLoad];
NSManagedObjectContext *context = [(iEatAppDelegate*)[[UIApplication sharedApplication] delegate] managedObjectContext];
addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:creator action:#selector(launchCreatorWindow)];
self.navigationItem.rightBarButtonItem = addButton;
NSFetchRequest *fetReq = [[NSFetchRequest alloc] init];
[fetReq setEntity:[NSEntityDescription entityForName:entityName inManagedObjectContext:context]];
NSError *e = nil;
NSArray *temp = [context executeFetchRequest:fetReq error:&e];
//leaking here according to performance tool
self.tableObjects = [[NSMutableArray alloc] initWithArray:temp];
if (e) {
NSLog(#"%#, %#", e, [e description]);
}
[fetReq release];
}
I tried releasing temp, but it crashed with the EXC_BAD_ACCESS which i believe means it is autoreleased already.
The leaks performance tool says it is a Category: CFArray (store-deque) Event: Malloc
also says my library is responsible. This is the stack trace, number 8 being my viewDidLoad frame:
0 CoreFoundation __CFAllocatorSystemAllocate
1 CoreFoundation CFAllocatorAllocate
2 CoreFoundation _CFAllocatorAllocateGC
3 CoreFoundation _CFArrayReplaceValues
4 CoreFoundation CFArrayReplaceValues
5 CoreFoundation -[__NSPlaceholderArray initWithObjects:count:]
6 CoreFoundation -[NSArray initWithArray:copyItems:]
7 CoreFoundation -[NSArray initWithArray:]
This one really has me stuck, any help is greatly appreciated.
This causes your leak:
self.tableObjects = [[NSMutableArray alloc] initWithArray:temp];
You create an array with a retain count of 1, then you use self.tableObjects which (if that property is marked as retain) brings the count up to 2.
Then in dealloc when you release the array the count is back down to 1, not 0 - so the array is never released.
Instead, just do this:
self.tableObjects = [NSMutableArray arrayWithArray:temp];
That returns an autoreleased array, so the eventual retain count will be only 1.
The answer is I am missing something basic.
[NSMutableArray alloc]
retain count = 1
self.tableObjects =
retain count = 2
dealloc
retain count still not 0
leak.
Sorry about this
This isn't the cause of your leak but I thought it'd be worth pointing out anyway: You should release addbutton right after you have assigned it to the rightBarButtonItem seeing as it is not going to be needed/used again:
self.navigationItem.rightBarButtonItem = addButton;
[addButton release];

initWithContentsOfURL leaks memory within NSOperation subclass. Anyone else seeing this?

I have been living on Instruments for last few hours staring at a puzzling memory leak. I have isolated it to this single line of code in an NSOperation subclass I wrote:
NSData *myData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:myURLString]];
Periodically this will leak 3500 bytes. Is anyone else seeing this? If so, is there a work around?
Thanks in advance.
UPDATE:
Here is the relevant section of code within the main() body of my NSOperation subclass:
- (void)main {
// ...
NSData *sequenceData =
[[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:concatenatedURLString]];
NSString *sequenceString =
[[NSString alloc] initWithBytes:[sequenceData bytes] length:[sequenceData length] encoding:NSUTF8StringEncoding];
NSDictionary *result = [NSDictionary dictionaryWithObjectsAndKeys:
self.chromosome, #"chromosome",
[NSNumber numberWithInt:self.basepairStart], #"basepairStart",
[NSNumber numberWithInt:self.basepairEnd], #"basepairEnd",
sequenceData, #"sequenceData",
sequenceString, #"sequenceString",
nil];
[sequenceData release];
[sequenceString release];
[self.target performSelectorOnMainThread:self.action withObject:result waitUntilDone:NO];
}
As you can see sequenceData and sequenceString are properly released. Also, I have confirmed that all ivars of this subclass (chromosome. etc.) are properly memory managed.
-Doug
You have to release or autorelease myData, otherwise it will leak according to the Cocoa Memory Management Rules

coredata memory leak

I have a CoreDataHelper static class that consists of 2 static methods. I ran my project through Clang and didn't find any leaks, however running through Instruments shows a leak that looks like it's related to CoreData. I have basic knowledge of memory management in objective-c and I believe I'm following the rules. However, I also think it's more likely there's something wrong with my code rather than a bug in Apple's CoreData stack. I'm running on the latest, Snow Leopard, iPhone SDK 3.1, XCode 3.2.
stack trace:
17 UIKit 528 Bytes -[UIApplication _run]
16 UIKit 64 Bytes -[UIApplication sendEvent:]
15 UIKit 64 Bytes -[UIApplication handleEvent:withNewEvent:]
14 UIKit 64 Bytes -[UIApplication _runWithURL:sourceBundleID:]
13 UIKit 16 Bytes -[UIApplication _performInitializationWithURL:sourceBundleID:]
12 helm 16 Bytes -[helmAppDelegate applicationDidFinishLaunching:] Classes/helmAppDelegate.m:113
11 helm 16 Bytes +[CoreDataHelper entityWithUIDexists:::] Classes/CoreDataHelper.m:50
10 helm 16 Bytes +[CoreDataHelper searchObjectsInContextCopy:::::] Classes/CoreDataHelper.m:39
9 CoreData 16 Bytes -[NSManagedObjectContext executeFetchRequest:error:]
8 CoreData 16 Bytes -[NSPersistentStoreCoordinator(_NSInternalMethods) executeRequest:withContext:]
7 CoreData 16 Bytes -[NSSQLCore executeRequest:withContext:]
6 CoreData 16 Bytes -[NSSQLiteConnection connect]
5 CoreData 16 Bytes -[NSSQLConnection createSchema]
4 CoreData 16 Bytes -[NSSQLConnection createTablesForEntities:]
3 CoreData 16 Bytes -[NSSQLConnection createTableForEntity:]
2 CoreData 16 Bytes -[NSSQLAdapter newCreateTableStatementForEntity:]
1 Foundation 16 Bytes -[NSCFString appendFormat:]
0 CoreFoundation 16 Bytes -[NSObject respondsToSelector:]
appdelegate:
BOOL b=[CoreDataHelper entityWithUIDexists:#"AddressBook" :context :[NSNumber numberWithInt:1]];
CoreDataHelper:
+(NSMutableArray *) searchObjectsInContextCopy: (NSString*) entityName : (NSManagedObjectContext *) managedObjectContext : (NSPredicate *) predicate : (NSString*) sortKey : (BOOL) sortAscending
{
NSLog(#"searchObjectsInContext");
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
// If a predicate was passed, pass it to the query
if(predicate != nil)
{
[request setPredicate:predicate];
}
// If a sort key was passed, use it for sorting.
if(sortKey != nil)
{
//NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:sortAscending selector: #selector(caseInsensitiveCompare:)];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:sortAscending selector: #selector(caseInsensitiveCompare:)];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
}
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
[request release];
return mutableFetchResults;
}
+(BOOL) entityWithUIDexists: (NSString *) entityName : (NSManagedObjectContext *) managedObjectContext : (NSNumber *) uid {
BOOL b;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(uid == %#)", uid];
NSMutableArray *ary=[self searchObjectsInContextCopy: entityName : managedObjectContext : predicate : nil : YES];
if(ary==nil) {
b=NO;
} else {
b=[ary count] >0 ? YES :NO;
}
[ary release];
return b;
}
Looking at your source code, I noticed two things. First, it is wrong to initialize a fetch request without sort descriptors. Quoting from the SDK 3.1 release notes:
"NSFetchedResultsController no longer crashes when fetch request has no sort descriptors. It’s still invalid to initialize an NSFetchedResultsController without sort descriptors, but a proper exception is now raised"
Therefore, you should always initialize your NSFetchedResultsController with sort descriptors.
The second thing is related to your leak. The executeFetchRequest: method return an autoreleased NSArray. You are using the mutableCopy method and you therefore are returning an object which has been retained by mutableCopy. That basically means that you are responsible for releasing the returned object. Again, quoting the mutableCopy method documentation:
"If you are using managed memory (not garbage collection), this method retains the new object before returning it. The invoker of the method, however, is responsible for releasing the returned object."
Ok, I found out something interesting. The nil sort descriptor wasn't causing the leak, it was still there but maybe I was stopping the leak detector too early. Here is the method with the leak. When I comment out the pragma mark, the 16 byte leak doesn't show in Instruments. Why would having a pragma mark in the method cause a 16 byte leak?
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *databaseFilePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"Locations.sqlite"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if([fileManager fileExistsAtPath:databaseFilePath])
{
# pragma mark - flag to delete file
NSError *fMerror;
if (![fileManager removeItemAtPath:databaseFilePath error:&fMerror]) {
NSLog(#"persistentStoreCoordinator error %#, %#", fMerror, [fMerror userInfo]);
}
}
NSURL *storeUrl = [NSURL fileURLWithPath: databaseFilePath];
NSError *error;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error]) {
NSLog(#"persistentStoreCoordinator error %#, %#", error, [error userInfo]); }
return persistentStoreCoordinator;
}