NSUserDefaults在iOS 8中不可靠

时间:2014-09-18 15:45:52

标签: ios objective-c ios8 nsuserdefaults

我有一个使用[NSUserDefaults standardUserDefaults]存储会话信息的应用。 通常,此信息会在应用启动时检查,并在应用退出时更新。 我发现它似乎在iOS 8中不可靠。

我目前正在iPad 2上进行测试,但如果需要,我可以在其他设备上进行测试。

有些时候,退出前写入的数据不会在应用启动时保留。同样,退出之前删除的键有时在启动后似乎存在。

我编写了以下示例,试图说明问题:

- (void)viewDidLoad 
{
    [super viewDidLoad];

    NSData *_dataArchive = [[NSUserDefaults standardUserDefaults] 
                                            objectForKey:@"Session"];

    NSLog(@"Value at launch - %@", _dataArchive);

    NSString *testString = @"TESTSTRING";
    [[NSUserDefaults standardUserDefaults] setObject:testString 
                                           forKey:@"Session"];
    [[NSUserDefaults standardUserDefaults] synchronize];

    _dataArchive = [[NSUserDefaults standardUserDefaults] 
                     objectForKey:@"Session"];

    NSLog(@"Value after adding data - %@", _dataArchive);

    [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"Session"];
    [[NSUserDefaults standardUserDefaults] synchronize];

    _dataArchive = [[NSUserDefaults standardUserDefaults] 
                     objectForKey:@"Session"];

    NSLog(@"Value before exit - %@", _dataArchive);

    exit(0);
}

运行上面的代码,我(通常)得到下面的输出(这是我期望的):

Value at launch - (null)
Value after adding data - TESTSTRING
Value after deleting data - (null)

如果我然后注释掉删除密钥的行:

//[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"Session"];
//[[NSUserDefaults standardUserDefaults] synchronize];

运行应用程序三次,我希望看到:

Value at launch - (null)
Value after adding data - TESTSTRING
Value after deleting data - TESTSTRING

Value at launch - TESTSTRING
Value after adding data - TESTSTRING
Value before exit - TESTSTRING

Value at launch - TESTSTRING
Value after adding data - TESTSTRING
Value before exit - TESTSTRING

但我实际看到的输出是:

Value at launch - (null)
Value after adding data - TESTSTRING
Value after deleting data - TESTSTRING

Value at launch - (null)
Value after adding data - TESTSTRING
Value after deleting data - TESTSTRING

Value at launch - (null)
Value after adding data - TESTSTRING
Value after deleting data - TESTSTRING

e.g。它似乎没有更新退出应用程序的价值。

编辑:我在运行iOS 7.1.2的iPad 2上测试了相同的代码;它似乎每次都能正常工作。

TLDR - 在iOS 8中,[NSUserDefaults standardUserDefaults]的工作不可靠吗?如果有的话有解决方法/解决方案吗?

12 个答案:

答案 0 :(得分:14)

iOS 8对NSUserDefaults引入了许多行为更改。虽然NSUserDefaults API变化不大,但行为的变化方式可能与您的应用程序相关。例如,不鼓励使用-synchronize(并且始终使用)。对基础和CoreFoundation的其他部分(例如文件协调)以及与共享容器相关的更改的添加更改可能会影响您的应用程序以及NSUserDefaults的使用。

特别是对NSUserDefaults的写作因此而改变了。编写需要更长时间,并且可能有其他进程竞争访问应用程序的用户默认存储。如果您在应用程序退出时尝试写入NSUserDefaults,则在某些情况下提交写入之前,您的应用程序可能会被终止。在您的示例中使用exit(0)强制终止很可能会刺激此行为。通常,当退出应用程序时,系统可以执行清理并等待未完成的文件操作完成 - 当您使用exit()或调试器终止应用程序时,这可能不会发生。

一般情况下,NSUserDefaults在iOS 8上正确使用时可靠。

Foundation release notes for OS X 10.10中描述了这些更改(目前还没有针对iOS 8的单独的Foundation发行说明)。

答案 1 :(得分:9)

看起来iOS 8不喜欢在NSUserDefaults中设置字符串。在保存之前尝试将字符串编码为NSData。

保存时:

[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:testString] forKey:@"Session"];

阅读时:

NSData *_data = [[NSUserDefaults standardUserDefaults] objectForKey:@"Session"];
NSString *_dataArchive = [NSKeyedUnarchiver unarchiveObjectWithData:_data];

希望这有帮助。

答案 2 :(得分:3)

正如gnasher729所说,不要调用exit()。在iOS8中,可能是NSUserDefaults的问题,但调用exit()根本不起作用。

你应该看到David Smith对NSUserDefaults(https://gist.github.com/anonymous/8950927)的评论:

  

异常终止应用程序(内存压力终止,崩溃,在Xcode中停止)就像git reset --hard HEAD,并且离开

答案 3 :(得分:2)

我发现NSUserDefaults在使用套件名称创建实例而不是依赖 $logged_in=$this->session->userdata('logged_in); 时在iOS 8.4上表现得很好。

CLLocationManager *locationManager = [[CLLocationManager alloc] init]; if([[UIDevice currentDevice].systemVersion floatValue]>=8.0) { CLAuthorizationStatus status = [CLLocationManager authorizationStatus]; // If the status is denied or only granted for when in use, display an alert if (status == kCLAuthorizationStatusDenied) { if (!isShowAlertView) { NSString *title = (status == kCLAuthorizationStatusDenied) ? @"Location services are off" : @"Background location is not enabled"; NSString *message = @"To use background location you must turn on 'Always' in the Location Services Settings"; } } // The user has not enabled any location services. Request background authorization. else if (status == kCLAuthorizationStatusNotDetermined) { [[APPDELEGATE locationManager] requestWhenInUseAuthorization]; } } else{ switch ([CLLocationManager authorizationStatus]) { case kCLAuthorizationStatusAuthorizedAlways: NSLog(@"Authorized Always"); break; case kCLAuthorizationStatusAuthorizedWhenInUse: NSLog(@"Authorized when in use"); break; case kCLAuthorizationStatusDenied: { NSLog(@"Denied"); if (&UIApplicationOpenSettingsURLString != NULL) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; } else { NSString *message = @"To use background location you must turn on 'Always' in the Location Services Settings"; } } break; case kCLAuthorizationStatusNotDetermined: NSLog(@"Not determined"); break; case kCLAuthorizationStatusRestricted: NSLog(@"Restricted"); break; default: break; } } locationManager.delegate = self; locationManager.desiredAccuracy = kCLLocationAccuracyBest; // 100 m [locationManager startUpdatingLocation]; //Delegate Methods of CLLocationManager - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation: (CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { NSLog(@"Latitude:- %f",newLocation.coordinate.latitude); NSLog(@"Longitude:- %f",newLocation.coordinate.longitude); } - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { NSLog(@"Error:- %@“,[error description]); }

答案 4 :(得分:1)

我对iOS 8有同样的问题,唯一对我有用的解决方案是延迟执行exit()函数一段时间(例如:0.1秒)使用:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 10), dispatch_get_main_queue(), ^{ exit(0); });

或创建方法,然后使用 performSelector:withObject:afterDelay:

调用它
- (void)exitApp {
   exit(0);
}

[self performSelector:@selector(exitApp) withObject:nil afterDelay:0.1];

答案 5 :(得分:0)

由于这是一个企业应用程序,而不是App Store应用程序,您可以尝试:

@interface UIApplication()
-(void) _terminateWithStatus:(int)status;
@end

然后致电:

[UIApplication.sharedApplication _terminateWithStatus:0];

它使用的是未记录的API,因此可能无法在iOS的早期版本或未来版本中使用。

答案 6 :(得分:0)

这是模拟器中的错误。这个bug也存在于iOS8 beta4之前的设备上。但是在设备上这个bug已经解决,但它目前存在于模拟器上。他们也改变了模拟器目录结构。如果你重置你的模拟器它将工作正常。在iOS8设备上,它也可以正常工作。

答案 7 :(得分:0)

我在Foundation Framework Reference中找到了它,认为它会很有用:

  

NSUserDefaults类提供了便于访问的方法   常见类型,例如浮点数,双精度数,整数,布尔值和URL。一个   默认对象必须是属性列表,即(或   对于集合的实例组合):NSData,NSString,   NSNumber,NSDate,NSArray或NSDictionary。 如果你想存储任何   在其他类型的对象中,通常应将其存档以创建   NSData的实例。有关更多详细信息,请参阅首选项和设置   编程指南。

答案 8 :(得分:0)

正如其他人已经指出使用exit()并且通常退出你的应用程序自己在iOS中是个坏主意。

但是我可能知道你需要处理什么。我们确实开发了一个企业应用程序,尽管我们试图说服客户,在iOS中它违反了所有规则和最佳实践,但他们坚持要求我们在某个时刻关闭应用程序。

我们使用这段代码代替exit():

UIApplication *app = [UIApplication sharedApplication];
[app performSelector:@selector(suspend)];

顾名思义,它只会暂停应用程序,就像用户按下主页按钮一样。因此,您的保存方法可能能够正确完成。

我还没有针对您的具体案例测试此解决方案,我不确定暂停对您是否足够,但对我们来说它有效。

答案 9 :(得分:0)

我通过仅在主线程中对NSUserDefaults进行更改来解决类似问题。

答案 10 :(得分:0)

我遇到了同样的问题。我通过调用

解决了这个问题

[[NSUserDefaults standardUserDefaults] synchronize];

之前打电话

[[NSUserDefaults standardUserDefaults] stringForKey:@"my_key"]

事实证明,不仅要在设定之后,还要在获得之前调用synchronize

答案 11 :(得分:-3)

在iOS应用程序中调用exit()是违法行为,正如您所注意到的那样,它受到了惩罚。您从不自己退出iOS应用程序。决不。