• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
NSUserDefaults deeply nested subtle mountain lion difference
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

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


  • Follow-Ups:
    • Re: NSUserDefaults deeply nested subtle mountain lion difference
      • From: Keary Suska <email@hidden>
  • Prev by Date: Re: Non-sandboxed Cocoa app accessing current user preferences using NSTask fails when launched outside Xcode.
  • Next by Date: Re: How to make text view treat colon as word boundary
  • Previous by thread: Developer.apple.com repair status
  • Next by thread: Re: NSUserDefaults deeply nested subtle mountain lion difference
  • Index(es):
    • Date
    • Thread