0

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.

the_critic
  • 12,720
  • 19
  • 67
  • 115
  • Why do you use a dictionary when an array would just work fine, since your 'string keys' are actually just some text plus the real index? – Christian Schnorr May 20 '12 at 19:42

1 Answers1

5

Basically, the problem is that you are setting the level dictionary for each level to the same stats object:

    for (int i = 1; i<=18; i++) {
        [levels setObject:stats forKey:[NSString stringWithFormat:@"level%d", i]];
    }

Instead, you need to set them each to a different mutable copy of the object:

   for (int i = 1; i<=18; i++) {
        NSMutableDictionary *statsCopy = [stats mutableCopy];
        [levels setObject:statsCopy forKey:[NSString stringWithFormat:@"level%d", i]];
        [statsCopy release];
    }

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 stats which 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:

 Top-A:
        Second-A-1
        Second-A-2
 Top-B:
        Second-B-1
        Second-B-2

To recap, you have the MyDictionary at 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 -mutableCopy of MyDictionary, the result is a new NSMutableDictionary with 2 keys (Top-A,Top-B), each associated with an NSMutableDictionary. However, these NSMutableDictionary objects are actually the same objects as the ones in MyDictionary. This is a shallow copy, meaning that there is a new top-level object (the mutable copy of MyDictionary), but that objects associated with each of the keys in the new dictionary are the same objects as were associated with the keys in MyDictionary. As such, if you change Top-A in the copy be associated with a different NSMutableDictionary, then the Top-A in MyDictionary and the copy will no longer be the same. But, if you change the value associated with Second-A-1, it will change both in MyDictionary and the copy of MyDictionary because Top-A points 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 MyDictionary will have a separate Top-A, Second-A-1, Second-A-2,Top-B, etc. from the ones that exist in MyDictionary, and therefore, you can change the values of any of the dictionaries (no matter how deep) without fear of changing other objects.

Community
  • 1
  • 1
gaige
  • 17,263
  • 6
  • 57
  • 68
  • Thank you very much. Unfortunately your solution only solved half of my problem. I did what you said and tried to make mutable copies for all the other dictionaries too. But still, In *every* levelSet's level1, the value is changed... I would really appreciate it if you could shed some light on this. – the_critic May 20 '12 at 14:56
  • so bascially, when the value in levelSet1's level1 changes, so does the value from level1 from levelSet2, levelSet3 and so forth. – the_critic May 20 '12 at 15:09
  • I've updated my answer to point out that you have the same problem in each of your loops. Every place you want independent values, you need to use a separate mutable copy of the NSMutableDictionary, otherwise if you assign the same NSMutableDictionary in multiple places, they will each reference the same objects. – gaige May 20 '12 at 16:08
  • well, like I said earlier, I had already tried to include those pieces of code into my loops, and I clearly understand why I do that. However, it still strangely does not reference the levelSet dictionary correctly... I've updated my question, to show what I did. – the_critic May 20 '12 at 18:36
  • I'll update my answer. I thought I was being clear about the lack of depth on the -mutableCopy, but I'll revise it. – gaige May 20 '12 at 19:02
  • I don't quite understand what a deep-copy is... So, making deep-copies of all these dictionaries will solve my problem ? If so, why ? – the_critic May 20 '12 at 20:12
  • Okay, I made deep-copies and it works. Thank you very much. However, if you could quickly explain what in this case the deep-copy does, I would really be grateful. – the_critic May 20 '12 at 20:24