为什么清除NSUserDefaults后创建UIWebView时会导致EXC_CRASH错误?

31
在我开始之前,我应该告诉你这只发生在iOS 5.1中。在最近的更新之前,从未发生过,也不会在任何其他版本上发生。话虽如此,这是发生了什么。
当用户注销我的应用程序时,会发生其中一件事情,即所有的NSUserDefaults都被删除。我不想手动删除每个密钥,因此我只是完全删除了所有的NSUserDefaults,使用了这个SO问题中建议的方法。
NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];

我发现每次在删除NSUserDefaults后尝试创建UIWebView时,会出现EXC_CRASH (SIGABRT)。 在调用[[UIWebView alloc] initWithFrame:frame]时发生崩溃。很奇怪吧?完全退出并重新打开应用程序可以再次创建UIWebView

因此,我设法弄清楚了删除默认值会导致UIWebView问题,但为了确保,我添加了一个-[NSUserDefaults setObject:forKey:]的符号断点。

-[NSUserDefaults setObject:forKey:] breakpoint

创建UIWebView确实触发了断点。

查看崩溃日志,我发现了异常原因:

-[__NSCFDictionary setObject:forKey:]: attempt to insert nil value (key: WebKitLocalStorageDatabasePathPreferenceKey)

以下是堆栈跟踪的开头:

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

目前我所做的并且有效的方法是,跟踪我设置的NSUserDefaults键并在需要时手动删除它们。但总有遗漏敏感键的风险,因此仅清除所有NSUserDefaults似乎更明智。因此,我想知道为什么我不能这样做。这是一个bug还是我做错了什么?

如果您需要更多信息,请告诉我!谢谢。

编辑:删除所有NSUserDefaults后执行[[NSUserDefaults standardUserDefaults] synchronize]也无效。


在同一个控制器中展示更多的代码。UIWebView是否依赖于存储在偏好设置中的任何值? - WrightsCS
EXC_CRASH 发生的独立于哪个控制器正在加载 UIWebView。我将所有 UIWebView 的初始化包装在 @try{} @catch{} 块中,每一个都给我相同的异常。我无法想象 UIWebView 可能依赖于任何值,特别是在初始化时。如果您仍然希望从包含 UIWebView 的控制器中获取一些代码,请告诉我。 - cbrauchli
是的,请发布所有相关代码。 - WrightsCS
老兄,我不知道你是怎么把UIWebView和NSUserDefaults联系起来的,但你刚刚让我省了三个小时的挫败感。 - Snowman
4个回答

25

我也遇到了这个问题,我认为你必须先创建一个UIWebView,然后再清除用户默认设置才能发生。在iOS5.1中,以下干净的项目将导致崩溃,但在iOS5.0及更早版本中运行良好:

UIWebView *webView = [[UIWebView alloc] init];
[webView release];

[[NSUserDefaults standardUserDefaults] setPersistentDomain:[NSDictionary dictionary] forName:[[NSBundle mainBundle] bundleIdentifier]];

UIWebView *anotherWebView = [[UIWebView alloc] init];
[anotherWebView release];

我可以通过这种方式绕过崩溃,而不必记住你所有的设置键:

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

使用这种方式是否存在任何问题?


啊,是的,那样就能重现这个 bug。感谢你的提示!另外,那似乎是一个避免崩溃的合理方法;我一直在使用它,没有发现任何问题。干杯 :) - cbrauchli
刚刚做了那个,看起来好像可以工作。我相信我们在管理用户注销时都会遇到这个问题。 - Nicolas Manzini
1
这是一个不错的修复方法,但看起来过于具体,可能无法防范系统可能依赖的其他未知密钥。花了一些时间,但我收集了我正在使用的每个密钥,并在用户注销时手动删除了每个密钥。 - Snowman
2
用户默认设置中不应该有任何系统关键键。这是对显然是苹果漏洞的一种解决方法。但如果您预计未来会有更多这样的漏洞,您可以跟踪自己的键。 - ppl
尝试了,但是还是不行……我还是遇到了崩溃……有什么想法吗? - Gik
WebKitLocalStorageDatabasePathPreferenceKey的值从iOS 6开始变成了字符串,将其设置为字典会导致其他类型的崩溃,例如在NSDictionary而不是NSString上调用-hasPrefix和-rangeOfString:。这将发生,因为UIWebView不希望有人在它们下面更改该值,因为它实际上是一个私有API。如果有人对此修复感兴趣,请确保您仅针对iOS 5.1进行目标定位! - Anurag

3
似乎您正在删除某种私有偏好设置,这可能是一个新的bug,可能与NSUserDefaults(您不应该能够删除它)或UIWebView(它应该处理缺少的条目)有关。
您是否尝试过其他回答中提到的方法(设置空字典)?这样做是否会产生相同的结果?
如果您获取NSUserDefaults的字典表示形式,获取所有键,并遍历它们来删除对象呢?(如果您需要代码示例,请告诉我)。

有道理。我曾尝试过将空字典设置为无效,但没有成功。我刚刚尝试了您的建议,遍历所有键,结果相同。查看字典,我发现有一堆我没有设置的键,大多数格式为 WebKit*。其中包括 WebKitLocalStorageDatabasePathPreferenceKey - cbrauchli
这听起来像是一个 bug。如果你在 iOS 5.0 或更早的版本上运行,你看到的键是否不同?当你提交 bug 报告时,这种信息将是必要的。 - jrturton
1
抱歉回复晚了。我创建了一个干净的应用程序,设置了NSUserDefaults中的一个键,然后删除了所有的NSUserDefaults,然后尝试创建UIWebView。我还无法在那里重现这个错误,所以我只能假设我的应用程序出了其他问题。我将提交一个错误报告并继续尝试重现该错误。我会在这里发布任何更新。谢谢! - cbrauchli

0

这对我有效

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

使用下面的代码将更加简单:

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

这很容易。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接