“NSUserDefaults 同步”怎么能跑得这么快?

Kud*_*oCC 5 mmap virtual-memory nsuserdefaults ios

在我的应用程序中,我想为每个用户登录将用户设置保存在 plist 文件中,我编写了一个名为的类,该类CCUserSettings具有几乎相同的界面NSUserDefaults,它读取和写入与当前用户 ID 相关的 plist 文件。它有效,但性能不佳。每次用户来电[[CCUserSettings sharedUserSettings] synchronize],我写了NSMutableDictionary(这使用户设置)的plist文件,下面的代码显示synchronizeCCUserSettings忽略了一些琐碎的细节。

- (BOOL)synchronize {
    BOOL r = [_settings writeToFile:_filePath atomically:YES];
    return r;
}
Run Code Online (Sandbox Code Playgroud)

我想,NSUserDefaults当我们调用应该写入文件[[NSUserDefaults standardUserDefaults] synchronize],但它运行非常快,我写了一个演示测试,关键部分是下面,运行1000次[[NSUserDefaults standardUserDefaults] synchronize],并[[CCUserSettings sharedUserSettings] synchronize]在我的iPhone6,结果是0.45秒VS9.16秒。

NSDate *begin = [NSDate date];
for (NSInteger i = 0; i < 1000; ++i) {
    [[NSUserDefaults standardUserDefaults] setBool:(i%2==1) forKey:@"key"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
NSDate *end = [NSDate date];
NSLog(@"synchronize seconds:%f", [end timeIntervalSinceDate:begin]);


[[CCUserSettings sharedUserSettings] loadUserSettingsWithUserId:@"1000"];
NSDate *begin = [NSDate date];
for (NSInteger i = 0; i < 1000; ++i) {
    [[CCUserSettings sharedUserSettings] setBool:(i%2==1) forKey:@"_boolKey"];
    [[CCUserSettings sharedUserSettings] synchronize];
}
NSDate *end = [NSDate date];
NSLog(@"CCUserSettings modified synchronize seconds:%f", [end timeIntervalSinceDate:begin]);
Run Code Online (Sandbox Code Playgroud)

结果显示,NSUserDefaults几乎比我的CCUserSettings. 现在我开始怀疑“每次调用synchronize方法时 NSUserDefaults 真的会写入 plist 文件吗?”,但如果不是,它如何保证在进程退出之前将数据写回文件(因为进程可能是随时被杀死)?

这些天我想出了一个想法来改进我的CCUserSettings,它是mmap Memory-mapped I/O。我可以将虚拟内存映射到文件,每次用户调用时synchronize,我都会创建一个NSDatawithNSPropertyListSerialization dataWithPropertyList:format:options:error:方法并将数据复制到该内存中,当进程退出时,操作系统会将内存写回文件。但是我可能不会得到很好的性能,因为文件大小不固定,每次数据长度增加时,我必须重新mmap虚拟内存,我相信操作很耗时。

抱歉,我的冗余细节,我只想知道如何NSUserDefaults实现如此出色的性能,或者任何人都可以提供一些好的建议来改进我的CCUserSettings?

Cat*_*Man 4

在现代操作系统(iOS 8+、macOS 10.10+)上,当您调用同步时,NSUserDefaults 不会写入文件。当您调用 -set* 方法时,它会向名为 cfprefsd 的进程发送一条异步消息,该进程存储新值、发送回复,然后在稍后的某个时间写出文件。-synchronize 所做的只是等待 cfprefsd 发送的所有未完成消息接收回复。

(编辑:如果愿意,您可以通过在 xpc_connection_send_message_with_reply 上设置符号断点然后设置用户默认值来验证这一点)