0

NSUserDefaults から取得した配列は不変であると読みました。辞書の配列があり、それらの辞書のいずれかのキーのオブジェクトを更新したい場合、配列全体および/または辞書の変更可能なコピーを作成する必要がありますか?

それぞれキー「イニング」を持つ複数の辞書を含む、キー「チーム」用に格納された配列を考えると、私は次を使用しています:

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

ここで正しいアプローチは何ですか?

4

2 に答える 2

2

解決策は、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];
于 2013-06-14T02:30:04.927 に答える
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);

1 回目の実行:

2013-07-26 18:01:55.064 Mbox Director-[デバッグ][15391:303] 起動中... (null) 2013-07-26 18:01:55.210 Mbox Director-[デバッグ][15391:303]初めて実行... { childDict = { MyNumber1 = 1; }; }

2回目の実行(すべてが正しいように見えます):

2013-07-26 18:02:54.999 Mbox Director-[デバッグ][15510:303] 起動中... { childDict = { MyNumber1 = 1; }; } 2013-07-26 18:02:55.000 Mbox Director-[デバッグ][15510:303] 終了中... { childDict = { MyNumber1 = 1; マイナンバー2 = 2; }; }

Mountain Lion の 3 回目の実行結果 (通知、起動時に MyNumber2 が見つからない...):

2013-07-26 17:39:48.760 Mbox Director-[デバッグ][15047:303] 起動中... { childDict = { MyNumber1 = 1; }; } 2013-07-26 17:39:48.760 Mbox Director-[デバッグ][15047:303] 終了中... { childDict = { MyNumber1 = 1; マイナンバー2 = 2; }; }

Lion の結果: 3 回目の実行 (通知、MyNumber2 が保存されました...): 2013-07-26 17:36:23.886 Mbox Director-[Debug][17013:120b] startup... {
childDict = { MyNumber1 = 1 ; マイナンバー2 = 2; }; } 2013-07-26 17:36:23.938 Mbox Director-[デバッグ][17013:120b] 終了中... { childDict = { MyNumber1 = 1; マイナンバー2 = 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];
于 2013-07-26T23:48:10.253 に答える