I have a singleton class where I set up a NSMutableDictionary called completedLevels.
This is how I set it up (in the init method of my singleton):
NSString *mainPath = [[NSBundle mainBundle] bundlePath];
NSString *levelConfigPlistLocation = [mainPath stringByAppendingPathComponent:@"levelconfig.plist"];
NSDictionary *levelConfig = [[NSDictionary alloc] initWithContentsOfFile:levelConfigPlistLocation];
completedLevels = [[NSMutableDictionary alloc]init];
NSMutableDictionary *levelSets = [[NSMutableDictionary alloc]init];
NSMutableDictionary *levels = [[NSMutableDictionary alloc]init];
NSMutableDictionary *stats = [[NSMutableDictionary alloc]init];
[stats setObject:[NSNumber numberWithBool:NO] forKey:@"levelDone"];
[stats setObject:[NSNumber numberWithInt:0] forKey:@"stars"];
[stats setObject:[NSNumber numberWithInt:0] forKey:@"time"];
[stats setObject:[NSNumber numberWithInt:0] forKey:@"bestTime"];
for (int i = 1; i<=18; i++) {
[levels setObject:stats forKey:[NSString stringWithFormat:@"level%d", i]];
}
for(int i= 1; i<=15;i++){
NSString *lvlSet = [NSString stringWithFormat:@"levelSet%d", i];
[levelSets setObject:levels forKey:lvlSet];
}
NSArray *categoriesArray = [levelConfig objectForKey:@"categoriesArray"];
for (int i=0; i<[categoriesArray count]; i++) {
NSString *category = [[levelConfig objectForKey:@"categoriesArray"]objectAtIndex:i];
[completedLevels setObject:levelSets forKey:category];
}
I want to explain my doings:
My intention was to create a dictionary in this form:
category = {
levelSet1 ={
level1 ={
bestTime = 0;
levelDone = 0;
stars = 0;
time = 0;
};
level2={
bestTime = 0;
levelDone = 0;
stars = 0;
time = 0;
};
.
.
.
}
levelSet2 ={
level1 ={
bestTime = 0;
levelDone = 0;
stars = 0;
time = 0;
};
level2={
bestTime = 0;
levelDone = 0;
stars = 0;
time = 0;
};
.
.
.
}
.
.
.
}
%d in the case of levelSet are integers from 1 to 15.
%d in the case of level are integers from 1 to 18.
I have several categories, and thus multiple sets of the example above.
This works well and upon calling NSLog, the dictionary appears in my console as it should.
The problem, however, arises when I want to change some entries in my dictionary as shown in the example below:
NSString *category = [[GameStateSingleton sharedMySingleton]getCurrentCategory];
NSString *levelSet = [NSString stringWithFormat:@"levelSet%d",[[GameStateSingleton sharedMySingleton]getSharedLevelSet]];
NSNumber *currentLevel = [NSNumber numberWithInt:[[GameStateSingleton sharedMySingleton]getSharedLevel]];
NSString *levelString = [NSString stringWithFormat:@"level%d", [currentLevel intValue]];
NSMutableDictionary *categories = [[NSMutableDictionary alloc]initWithDictionary:
[[GameStateSingleton sharedMySingleton]getCompletedLevels]];
[[[[categories objectForKey:category]objectForKey:levelSet]objectForKey:levelString]setObject:[NSNumber numberWithBool:YES] forKey:@"levelDone"];
[[GameStateSingleton sharedMySingleton]setCompletedLevels:categories];
NSLog(@"%@",[[GameStateSingleton sharedMySingleton]getCompletedLevels]);
To explain that:
When the player is done with a level, I want the levelDone entry to change its value. But when I log it afterwards, suddenly all levelDone entries of all categories change to the BOOLEAN value of 1. Why is that ?
——————- update ———————–
completedLevels = [[NSMutableDictionary alloc]init];
NSMutableDictionary *levelSets = [[NSMutableDictionary alloc]init];
NSMutableDictionary *levels = [[NSMutableDictionary alloc]init];
NSMutableDictionary *stats = [[NSMutableDictionary alloc]init];
[stats setObject:[NSNumber numberWithBool:NO] forKey:@"levelDone"];
[stats setObject:[NSNumber numberWithInt:0] forKey:@"stars"];
[stats setObject:[NSNumber numberWithInt:0] forKey:@"time"];
[stats setObject:[NSNumber numberWithInt:0] forKey:@"bestTime"];
for (int i = 1; i<=18; i++) {
NSMutableDictionary *statsCopy = [stats mutableCopy];
[levels setObject:statsCopy forKey:[NSString stringWithFormat:@"level%d", i]];
[statsCopy release];
}
for(int i= 1; i<=15;i++){
NSString *lvlSet = [NSString stringWithFormat:@"levelSet%d", i];
NSMutableDictionary *levelsCopy = [levels mutableCopy];
[levelSets setObject:levelsCopy forKey:lvlSet];
[levelsCopy release];
}
NSArray *categoriesArray = [levelConfig objectForKey:@"categoriesArray"];
for (int i=0; i<[categoriesArray count]; i++) {
NSString *category = [[levelConfig objectForKey:@"categoriesArray"]objectAtIndex:i];
NSMutableDictionary *levelSetsCopy = [levelSets mutableCopy];
[completedLevels setObject:levelSetsCopy forKey:category];
[levelSetsCopy release];
}
The part where I retrieve and set it stayed the same…
_________________ SOLUTION ____________________
NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDictionary, kCFPropertyListMutableContainers);
I made deep-copies with this method.
Basically, the problem is that you are setting the level dictionary for each level to the same stats object:
Instead, you need to set them each to a different mutable copy of the object:
That should solve the first part for you. However, you also have the same problem with the levelSets. Basically, whenever you add a mutable dictionary, you need to determine whether you are trying to point at the same mutable object (you want to keep them synchronized), or whether you want copies of that mutable object (thus, they act independently). And when you look at building this kind of tree structure, that means you need to look at this at every level.
So, looking at the rest of your code, you’ll also need to fix your level sets and categories in the same way, by making a mutable copy inside of each loop and then adding that mutable copy instead of adding the original object. And, since you want to mutate the contents of the contents of the dictionaries, every time you make a mutable copy that contains another mutable dictionary, you need to make that copy at each depth.
You are going to have to choose between building these things depth-wise or doing a deep copy. There’s another question:
deep mutable copy of a NSMutableDictionary
That covers deep copies of mutable dictionaries pretty well. Alternatively, you can invert the building of the structure so that instead of making copies, you build each dictionary except for
statswhich you can easily copy.The deep copy creates copies of each of the mutable entries all the way down to the farthest leaf nodes. If you think about the NSMutableDictionaries as a tree with the base of the tree at the top ( completedLevels in your case) and the leaves as the elements of the stats NSMutableDictionaries that you copy, you want to individually manipulate every element of the tree, whether it is the leaf node or any intermediate.
An illustration could be made as follows, with this representing the contents of
MyDictionary:To recap, you have the
MyDictionaryat the top, which has 2 keys (Top-A,Top-B), each of which is associated with a separate NSMutableDictionary object. Each of those NSMutableDictionaries has 2 keys, each associated with a separate object.When you make a
-mutableCopyofMyDictionary, the result is a newNSMutableDictionarywith 2 keys (Top-A,Top-B), each associated with an NSMutableDictionary. However, theseNSMutableDictionaryobjects are actually the same objects as the ones inMyDictionary. This is a shallow copy, meaning that there is a new top-level object (the mutable copy ofMyDictionary), but that objects associated with each of the keys in the new dictionary are the same objects as were associated with the keys inMyDictionary. As such, if you changeTop-Ain the copy be associated with a differentNSMutableDictionary, then theTop-AinMyDictionaryand the copy will no longer be the same. But, if you change the value associated withSecond-A-1, it will change both inMyDictionaryand the copy ofMyDictionarybecauseTop-Apoints at a single object.When you make a deep copy, the utility copies every element of every dictionary individually, which means that the copy of
MyDictionarywill have a separateTop-A,Second-A-1,Second-A-2,Top-B, etc. from the ones that exist inMyDictionary, and therefore, you can change the values of any of the dictionaries (no matter how deep) without fear of changing other objects.