更新NSUserDefaults字典中的密钥(内部数组)

时间:2013-06-14 02:19:52

标签: nsarray plist nsdictionary nsuserdefaults

我已经读过从NSUserDefaults检索的数组是不可变的。如果我有一个字典数组,并且我想更新其中一个字典上的键的对象,我是否必须制作整个数组和/或字典的可变副本?

给定一个为关键'团队'存储的数组,包含多个字典,每个字典都有一个键'Innings',我正在使用:

NSMutableArray *teams = [[[NSUserDefaults standardUserDefaults] objectForKey:@"Teams"] mutableCopy];
NSMutableDictionary *teamDictionary = [teams objectAtIndex:_selectedIndex.row];
[teamDictionary setObject:@99 forKey:@"Innings"];
[[NSUserDefaults standardUserDefaults] setObject:teams forKey:@"Teams"];
[[NSUserDefaults standardUserDefaults] synchronize];

但是我正在接受:

mutating method sent to immutable object

这里的正确方法是什么?

2 个答案:

答案 0 :(得分:2)

解决方案是使用NSDictionary的可变副本。

表示数组的可变副本不是“深层复制” - 里面的字典仍然是不可变的。

所以我不得不在字典中创建一个可变的copt,更新它,然后用副本替换原始字典。

NSMutableArray *teams = [[[NSUserDefaults standardUserDefaults] objectForKey:@"Teams"] mutableCopy];
NSMutableDictionary *teamDictionary = [[teams objectAtIndex:_selectedIndex.row] mutableCopy];
[teamDictionary setObject:@99 forKey:@"Innings"];
[teams replaceObjectAtIndex:_selectedIndex.row withObject:teamDictionary];
[[NSUserDefaults standardUserDefaults] setObject:teams forKey:@"Teams"];
[[NSUserDefaults standardUserDefaults] synchronize];

答案 1 :(得分:0)

来自Apple Docs: 即使您将可变对象设置为值,NSUserDefaults返回的值也是不可变的。例如,如果将可变字符串设置为“MyStringDefault”的值,则稍后使用stringForKey:检索的字符串将是不可变的。

Apple博士一直都这么说。在实践中,字典和数组一直是可变的, 尽管Apple有警告,只要你使用同步。 Mountain Lion的不同之处在于,现在,如果您读/写一个深层嵌套的字典,那些深层嵌套 对象不会保存到NSUserDefaults。

他们甚至可能看起来已经保存,因为您可以在退出应用之前阅读这些值。 巧妙的是,当你重新启动时,它们并不存在。

更糟糕的是,制作mutableCopy并不能解决问题。 只有制作mutableCopyDeepPropertyList才能解决问题(参见下面的解决方案)

在Mountain Lion之前,像这样的代码有效,即使文档表明它不应该

  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];
     NSLog( @"first time run... %@", parentDict );
     exit(0);
  }

  NSMutableDictionary *childDict = [parentDict objectForKey: @"childDict"];
  [childDict removeObjectForKey:@"MyNumber2"];
  [childDict setObject: @"2" forKey: @"MyNumber2"];

  [[NSUserDefaults standardUserDefaults] setObject: parentDict forKey: @"parentDict"];
  [[NSUserDefaults standardUserDefaults] synchronize];
  // Now read the value back to verify it:
  parentDict = [[NSUserDefaults standardUserDefaults] objectForKey:@"parentDict"];
  NSLog( @"exiting... %@", parentDict );
  exit(0);
  

第一轮:

     

2013-07-26 18:01:55.064 Mbox Director- [Debug] [15391:303]开始   up ...(null)2013-07-26 18:01:55.210 Mbox   导演 - [调试] [15391:303]第一次运行...... {         childDict = {             MyNumber1 = 1;         }; }

     

第二次运行(所有看起来正确):

     

2013-07-26 18:02:54.999 Mbox Director- [Debug] [15510:303]开始   ...... {         childDict = {             MyNumber1 = 1;         }; 2013-07-26 18:02:55.000 Mbox Director- [Debug] [15510:303]退出... {         childDict = {             MyNumber1 = 1;             MyNumber2 = 2;         }; }

     

山狮第3次跑步的结果(通知,MyNumber2失踪时   启动...):

     

2013-07-26 17:39:48.760 Mbox Director- [Debug] [15047:303]开始   ...... {         childDict = {             MyNumber1 = 1;         }; 2013-07-26 17:39:48.760 Mbox Director- [Debug] [15047:303]退出... {         childDict = {             MyNumber1 = 1;             MyNumber2 = 2;         }; }

     

Lion中的结果:第3次运行(通知,MyNumber2已保存......):2013-07-26   17:36:23.886 Mbox Director- [Debug] [17013:120b]启动...... {
  childDict = {          MyNumber1 = 1;          MyNumber2 = 2; }; 2013-07-26 17:36:23.938 Mbox Director- [Debug] [17013:120b]退出... {childDict = {          MyNumber1 = 1;          MyNumber2 = 2; }; }

  // 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

用法:

  NSMutableDictionary *dict = [[NSUserDefaults standardUserDefaults] objectForKey:@"mydictionary"];
  dict = [[dict mutableCopyDeepPropertyList] autorelease];