NSUserDefaults deeply nested subtle mountain lion difference
NSUserDefaults deeply nested subtle mountain lion difference
- Subject: NSUserDefaults deeply nested subtle mountain lion difference
- From: Keith Knauber <email@hidden>
- Date: Fri, 26 Jul 2013 23:15:37 +0000
- Thread-topic: NSUserDefaults deeply nested subtle mountain lion difference
From Apple Docs:
Values returned from NSUserDefaults are immutable, even if you set a mutable object as the value. For example, if you set a mutable string as the value for "MyStringDefault", the string you later retrieve usingstringForKey: will be immutable.
The Apple docs have always said this. In practice, dictionaries and arrays have always been mutable,
despite Apple's warning, as long as you used synchronize.
The difference in Mountain Lion is that now, if you read/write a deeply nested dictionary, those deeply nested
objects are not saved to NSUserDefaults.
They may even look like they've been saved because you can read the values back right before you exit your app.
Subtly, they are not there when you relaunch.
Even worse, making a mutableCopy doesn't solve the problem.
Only making a mutableCopyDeepPropertyList solves the problem (see solution below)
Before Mountain Lion, code like this worked, even though the docs suggest that it shouldn't
NSMutableDictionary *parentDict = [[NSUserDefaults standardUserDefaults] objectForKey:@"parentDict"];
NSLog( @"starting up... %@", parentDict );
if ( !parentDict )
{
NSMutableDictionary *childDict = [NSMutableDictionary dictionaryWithObject: @"1" forKey: @"MyNumber1"];
parentDict = [NSMutableDictionary dictionaryWithObject:childDict forKey: @"childDict"];
[[NSUserDefaults standardUserDefaults] setObject: parentDict forKey: @"parentDict"];
[[NSUserDefaults standardUserDefaults] synchronize];
exit(0);
}
NSMutableDictionary *childDict = [parentDict objectForKey: @"childDict"];
[childDict removeObjectForKey:@"MyNumber2"];
[childDict setObject: @"2" forKey: @"MyNumber2"];
[[NSUserDefaults standardUserDefaults] setObject: parentDict forKey: @"parentDict"];
[[NSUserDefaults standardUserDefaults] synchronize];
NSLog( @"exiting... %@", parentDict );
exit(0);
1st run:
2013-07-26 18:01:55.064 Mbox Director-[Debug][15391:303] starting up... (null)
2013-07-26 18:01:55.210 Mbox Director-[Debug][15391:303] first time run... {
childDict = {
MyNumber1 = 1;
};
}
2nd run (everything *looks* correct):
2013-07-26 18:02:54.999 Mbox Director-[Debug][15510:303] starting up... {
childDict = {
MyNumber1 = 1;
};
}
2013-07-26 18:02:55.000 Mbox Director-[Debug][15510:303] exiting... {
childDict = {
MyNumber1 = 1;
MyNumber2 = 2;
};
}
Results in Mountain Lion
3rd run (notice, MyNumber2 missing when starting up...):
2013-07-26 17:39:48.760 Mbox Director-[Debug][15047:303] starting up... {
childDict = {
MyNumber1 = 1;
};
}
2013-07-26 17:39:48.760 Mbox Director-[Debug][15047:303] exiting... {
childDict = {
MyNumber1 = 1;
MyNumber2 = 2;
};
}
Results in Lion:
3rd run (notice, MyNumber2 got saved...):
2013-07-26 17:36:23.886 Mbox Director-[Debug][17013:120b] starting up... {
childDict = {
MyNumber1 = 1;
MyNumber2 = 2;
};
}
2013-07-26 17:36:23.938 Mbox Director-[Debug][17013:120b] exiting... {
childDict = {
MyNumber1 = 1;
MyNumber2 = 2;
};
}
Solution:
// This function makes a deep mutable copy. NSDictionary and NSArray mutableCopy does not create a DEEP mutableCopy.
// We accomplish a deep copy by first serializing the dictionary
// to a property list, and then unserializing it to a guaranteed deep copy.
// It requires that your array is serializable, of course.
// This method seems to be more bulletproof than some of the other implementations
// available on the web.
//
// Follows copy rule... you are responsible for releasing the returned object.
// Returns nil if not serializable!
id mutableCopyFromPlist( id plist )
{
NSError *error = nil;
@try
{
#ifdef MAC_OS_X_VERSION_10_6
NSData *binData = [NSPropertyListSerialization dataWithPropertyList:plist
format:NSPropertyListBinaryFormat_v1_0
options:0
error:&error];
NSString *errorString = [error localizedDescription];
#else
NSString *errorString = nil;
NSData *binData = [NSPropertyListSerialization dataFromPropertyList:plist
format:NSPropertyListBinaryFormat_v1_0
errorDescription:&errorString];
#endif
if (errorString || !binData )
{
DLogErr( @"error serializing property list %@", errorString );
}
else
{
#ifdef MAC_OS_X_VERSION_10_6
NSError *error = nil;
id deepCopy = [NSPropertyListSerialization
propertyListWithData:binData
options:NSPropertyListMutableContainersAndLeaves
format:NULL
error:&error];
errorString = [error localizedDescription];
#else
id deepCopy = [NSPropertyListSerialization
propertyListFromData:binData
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:NULL
errorDescription:&errorString];
#endif
[deepCopy retain]; // retain this so that we conform to the 'copy rule'... our function name contains the work 'Copy'
if (errorString)
{
DLogErr( @"error serializing property list %@", errorString );
}
else
{
return deepCopy;
}
}
}
@catch (NSException *exception )
{
DLogErr( @"error serializing property list %@", [error localizedDescription] );
}
return nil; // couldn't make a deep copy... probably not serializable
}
@implementation NSDictionary (VNSDictionaryCategory)
// This function makes a deep mutable copy. NSDictionary's mutableCopy does not create a DEEP mutableCopy.
// We accomplish a deep copy by first serializing the dictionary
// to a property list, and then unserializing it to a guaranteed deep copy.
// It requires that your dictionary is serializable, of course.
// This method seems to be more bulletproof than some of the other implementations
// available on the web.
//
// Follows copy rule... you are responsible for releasing the returned object.
// Returns nil if not serializable!
-(NSMutableDictionary *)mutableCopyDeepPropertyList
{
return mutableCopyFromPlist( self );
}
@end
#pragma mark -
@implementation NSArray (VNSArrayCategory)
// This function makes a deep mutable copy. NSDictionary's mutableCopy does not create a DEEP mutableCopy.
// We accomplish a deep copy by first serializing the dictionary
// to a property list, and then unserializing it to a guaranteed deep copy.
// It requires that your array is serializable, of course.
// This method seems to be more bulletproof than some of the other implementations
// available on the web.
//
// Follows copy rule... you are responsible for releasing the returned object.
// Returns nil if not serializable!
-(NSMutableArray *)mutableCopyDeepPropertyList
{
return mutableCopyFromPlist( self );
}
@end
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden