【问题标题】:Updating a key in NSUserDefaults dictionary (inside array)更新 NSUserDefaults 字典中的键(在数组内)
【发布时间】:2013-06-14 02:19:51
【问题描述】:

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

给定一个为键“Teams”存储的数组,其中包含多个字典,每个字典都有一个键“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

这里的正确方法是什么?

【问题讨论】:

    标签: nsarray plist nsdictionary nsuserdefaults


    【解决方案1】:

    解决方案也是使用 NSDictionary 的可变副本。数组的可变副本不是“深层副本” - 里面的字典保持不变。

    所以我还必须制作一个可变字典,更新它,然后用副本替换原始字典。

    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];
    

    【讨论】:

      【解决方案2】:

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

      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] 开始 向上... (null) 2013-07-26 18:01:55.210 Mbox Director-[Debug][15391:303] 第一次运行... { childDict = { 我的号码1 = 1; }; }

      第二次运行(一切看起来正确):

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

      Mountain Lion 第 3 次运行的结果(注意,MyNumber2 丢失时 正在启动...):

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

      Lion 中的结果:第 3 次运行(注意,MyNumber2 已保存...):2013-07-26 17:36:23.886 Mbox Director-[Debug][17013:120b] 正在启动... {
      childDict = { 我的号码1 = 1; MyNumber2 = 2; }; } 2013-07-26 17:36:23.938 Mbox Director-[Debug][17013:120b] 正在退出... { childDict = { 我的号码1 = 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];
      

      【讨论】:

        猜你喜欢
        • 2023-03-10
        • 1970-01-01
        • 2018-04-16
        • 1970-01-01
        • 2020-05-27
        • 1970-01-01
        • 1970-01-01
        • 2021-07-28
        • 1970-01-01
        相关资源
        最近更新 更多