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.
Could you test this? (added the mutableCopy for the gameBoard);
EDIT
could you change the above to:
Could you try this? It might not be the most beautiful solution but i believe it will work.
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 fromNSUserDefaults
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 theNSPropertyListMutableContainers
orNSPropertyListMutableContainersAndLeaves
options.