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 (Cocoa-dev@lists.apple.com)

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:
https://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to