Strange “mutating method sent to immutable object”

2019-07-19 18:34发布

问题:

This is a strange one...

Periodically I am checking if the user has achieved a new top score in my app. Here is my code:

- (void) newTopScore
{
    // pull old top score from the database
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSDictionary *getBoardsAndScores = [defaults dictionaryRepresentation];
    NSMutableArray *boardsAndScores = [[getBoardsAndScores objectForKey:@"boardsAndScores"] mutableCopy];
    NSMutableDictionary *boardAndScoreObject = [[boardsAndScores objectAtIndex:gameBoardIndex] mutableCopy];
    NSString *topscore = [boardAndScoreObject objectForKey:@"topscore"];  

    NSLog(@"topscore in value: %i", [topscore intValue]);

    if ([topscore intValue] == 0)
    {
        NSString *topscoreString = [[NSString alloc] initWithFormat:@"%i", [self countCurrentPegs]];

        NSMutableArray *mutableBoardsAndScores = [boardsAndScores mutableCopy];

        [[mutableBoardsAndScores objectAtIndex:gameBoardIndex] setObject:topscoreString forKey:@"topscore"];

        [defaults setObject:mutableBoardsAndScores forKey:@"boardsAndScores"];
        [defaults synchronize];

    }

    else if ([self countCurrentPegs] < [topscore intValue])
    {
        NSString *topscoreString = [[NSString alloc] initWithFormat:@"%i", [self countCurrentPegs]];

        NSMutableArray *mutableBoardsAndScores = [boardsAndScores mutableCopy];

        [[mutableBoardsAndScores objectAtIndex:gameBoardIndex] setObject:topscoreString forKey:@"topscore"];

        [defaults setObject:mutableBoardsAndScores forKey:@"boardsAndScores"];
        [defaults synchronize];
    }

}

Sometimes the code works just fine, both for the if and the else. Sometimes though I get the following error:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFDictionary setObject:forKey:]: 
mutating method sent to immutable object'

I've tried setting break points and stepping through the program line by line. The crash seems somewhat random and I can't work out a pattern. It usually crashes on the second...

[[mutableBoardsAndScores objectAtIndex:gameBoardIndex] setObject:topscoreString forKey:@"topscore"];

...line.

The object IS mutable. Or at least it was. Why is it changing?

Any help of advice would be very appreciated.

Thanks.

回答1:

[[[mutableBoardsAndScores objectAtIndex:gameBoardIndex] mutableCopy] setObject:topscoreString forKey:@"topscore"];

Could you test this? (added the mutableCopy for the gameBoard);

EDIT

could you change the above to:

NSMutableArray *gameBoard = [[mutableBoardsAndScores objectAtIndex:gameBoardIndex] mutableCopy];

[gameBoard setObject:topscoreString forKey:@"topscore"];

[mutableBoardsAndScores removeObjectAtIndex:gameBoardIndex];
[mutableBoardsAndScores insertObject:gameBoard atIndex:gameBoardIndex];

Could you try this? It might not be the most beautiful solution but i believe it will work.



回答2:

NSUserDefaults does not store the objects you set to it. It stores the data. Effectively, it's making a copy. And any collection object you retrieve from NSUserDefaults will be immutable.

Edited to say: also, when you make a mutable copy of a collection, it's a shallow copy. You get a mutable collection holding the same objects as the original collection. If the original collection held immutable objects, then so will the new collection.

If you need to do a deep copy of a property-list compatible hierarchy of objects, getting mutable objects back, you can use NSPropertyListSerialization. Combine +dataWithPropertyList:format:options:error: with +propertyListWithData:options:format:error: and the NSPropertyListMutableContainers or NSPropertyListMutableContainersAndLeaves options.