In my app I want to save user settings in a plist file for each user logs in, I write one class called CCUserSettings
which has almost the same interface as NSUserDefaults
and it reads and writes a plist file related to the current user id. It works but has poor performance. Every time user calls [[CCUserSettings sharedUserSettings] synchronize]
, I write a NSMutableDictionary
(which keep the user settings) to a plist file, the code below shows synchronize
of CCUserSettings
omitting some trivial details.
- (BOOL)synchronize {
BOOL r = [_settings writeToFile:_filePath atomically:YES];
return r;
}
I suppose NSUserDefaults
should write to files when we call [[NSUserDefaults standardUserDefaults] synchronize]
, but it runs really fast, I write a demo to test, the key part is below, run 1000 times [[NSUserDefaults standardUserDefaults] synchronize]
and [[CCUserSettings sharedUserSettings] synchronize]
on my iPhone6, the result is 0.45 seconds vs 9.16 seconds.
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]);
As the result shows, NSUserDefaults
is almost 20 times faster than my CCUserSettings
. Now I start to wonder that "Does NSUserDefaults really write to the plist files every time we call synchronize
method?", but if it doesn't, how can it guarantee the data write back to file before the process exits(as the process may be killed at any time)?
These days I come up with an idea to improve my CCUserSettings
, it is mmap
Memory-mapped I/O. I can map a virtual memory to a file and every time user calls synchronize
, I create a NSData
with NSPropertyListSerialization dataWithPropertyList:format:options:error:
method and copy the data into that memory, operating system will write memory back to file when process exits. But I may not get a good performance because the file size is not fixed, every time the length of data increases, I have to remmap
a virtual memory, I believe the operation is time consuming.
Sorry for my redundant details, I just want to know how NSUserDefaults
works to achieve so good performance, or can anyone have some good advices to improve my CCUserSettings
?
On modern operating systems (iOS 8+, macOS 10.10+), NSUserDefaults does not write the file when you call synchronize. When you call -set* methods, it sends an async message to a process called cfprefsd, which stores the new values, sends a reply, and then at some later time writes the file out. All -synchronize does is wait for all outstanding messages to cfprefsd to receive replies.
(edit: you can verify this, if you like, by setting a symbolic breakpoint on xpc_connection_send_message_with_reply and then setting a user default)