31

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:].

-[NSUserDefaults setObject:forKey:] breakpoint

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.

Community
  • 1
  • 1
cbrauchli
  • 1,555
  • 15
  • 25
  • Show more code you are using in that same controller. Does the UIWebView depend on any values stored in the preferences? – WrightsCS Mar 13 '12 at 06:54
  • The `EXC_CRASH` happens independently of which controller is loading the UIWebView. I wrapped all of the UIWebView inits in `@try{} @catch{}` blocks and every one gives me the same exception. I cannot think of any values that UIWebView might depend upon, especially at initialization-time. If you'd still like some code from one the controllers containing a UIWebView, let me know. – cbrauchli Mar 13 '12 at 07:04
  • yes, post any and all relevant code. – WrightsCS Mar 13 '12 at 16:35
  • Man I don't know how you made the connection between UIWebView and NSUserDefaults, but you just saved me a frustrating 3 hours – Snowman Sep 05 '12 at 12:13

4 Answers4

25

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?

ppl
  • 281
  • 2
  • 5
  • Ah yes, that reproduces the bug. Thanks for the tip! Also, that seems like a reasonable way to avoid the crash; I've been using it and have not noticed any problems with it. Cheers :) – cbrauchli Mar 16 '12 at 19:09
  • just did that and it seems to work. I'm sure we all get this problem when managing a user log out. – Nicolas Manzini Jul 05 '12 at 11:43
  • 1
    This is a good fix, but looks way too specific, and might not guard against other unknown keys the system might be relying on. Took some time, but I instead collected every key I was using and removed each one manually when the user logs out. – Snowman Sep 05 '12 at 12:15
  • 2
    There shouldn't be ANY system critical keys in user defaults. This is a workaround for what is clearly an Apple bug. But if you are expecting there to be more such bugs in the future, you can certainly track your own keys. – ppl Sep 06 '12 at 20:40
  • Try it, but does not work... I still get a crash... Any ideas? – Gik Mar 20 '13 at 21:54
  • The value for `WebKitLocalStorageDatabasePathPreferenceKey` has become a string iOS 6 onwards and setting it to a dictionary causes other types of crashes such as -hasPrefix and -rangeOfString: being called on an NSDictionary instead of an NSString. That will happen because UIWebView doesn't expect someone to change the value from underneath them because it's a private API for all practical purposes. If anyone is interested in this fix, do make sure that you're targeting iOS 5.1 and iOS 5.1 alone! – Anurag Oct 31 '14 at 17:56
3

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).

jrturton
  • 118,105
  • 32
  • 252
  • 268
  • That makes sense. I had tried the other method of setting a blank dictionary to no avail. I just tried your suggestion of iterating through all the keys and that produces the same result. Looking at the dictionary, I see a bunch of keys that I did not set, mostly of the format `WebKit*`. `WebKitLocalStorageDatabasePathPreferenceKey` is among those set. – cbrauchli Mar 13 '12 at 07:22
  • This sounds like a bug, then. If you run on iOS 5.0 or earlier, are the keys that you see different? That sort of information will be needed when you file the bug. – jrturton Mar 13 '12 at 07:57
  • 1
    Sorry for the delay in getting back to you. I created a clean app that sets a key in NSUserDefaults, then deletes all of the NSUserDefaults, then tries to create UIWebView. I've been unable to reproduce the bug there yet, so I can only assume something else is going wrong in my app. I'll file a bug report and keep on trying to reproduce the bug. I'll post here with any updates. Thanks! – cbrauchli Mar 15 '12 at 21:08
0

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];
0

Simpler it will be to use the code below:

[self saveValue:@"" forKey:@"WebKitLocalStorageDatabasePathPreferenceKey"];
[[NSUserDefaults standardUserDefaults] synchronize];

It is easier.