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:

  • -fembed-bitcode is not supported on versions of iOS prior to 6.0
  • Move view when so that keyboard does not hide text field
  • Text get overlapped while scrolling in UI table view?
  • Swift 3 Value of type 'Any?' has no member 'object'
  • How do I retrieve a page number or page reference for an Outline destination in a PDF on iOS?
  • UITextField - UIKeyboardTypeDecimalPad on iPad?
  • 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.

    4 Solutions Collect From Internet About “Why does clearing NSUserDefaults cause EXC_CRASH later when creating a UIWebView?”

    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.