A mutation method sent to an immutable object closes the application

In my code, when the user completes a level, it will unlock the next level (of 15 levels).

However, I noticed that when I complete the level, the first time, it works, however, when I go back to it and try again, it crashes with this error;
[__NSCFArray replaceObjectAtIndex:withObject:]: mutating method sent to immutable object

      

This piece of code:

//Unlock Next Level
                if (levelNumber != 15) {
                    [m_appDelegate.levels replaceObjectAtIndex:levelNumber withObject:[NSNumber numberWithBool:NO]];
                    [m_appDelegate saveLevels];
                }

      

If I remove this line,

[m_appDelegate.levels replaceObjectAtIndex:levelNumber withObject:[NSNumber numberWithBool:NO]];

      

The app then doesn't crash, but of course it doesn't unlock further levels.

Some links below that might help;

int levelNumber;
@property (nonatomic, readwrite) int levelNumber;


- (void) actionLockedLevel:(id)sender {
    selectedLevel = ((CCNode*)sender).tag;
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Level Locked" message:@"Level is locked. Pass the previous level first." delegate:nil cancelButtonTitle:@"No" otherButtonTitles:nil];
    [alert show];
}

- (void) actionUnlockedLevel:(id)sender {

    CCScene *scene = [CCScene node];
    TimedLevel *layer = [TimedLevel node];
    layer.levelNumber = ((CCNode*)sender).tag;
    [scene addChild:layer];


    [[CCDirector sharedDirector] replaceScene:scene];
}

      

The levels are called in AppDelegate.m:

- (void) loadLevels {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    if ([defaults objectForKey:@"LEVELS"] == nil) {

        levels = [[NSMutableArray alloc] initWithObjects:
                  [NSNumber numberWithBool:NO],
                  [NSNumber numberWithBool:YES],
                  [NSNumber numberWithBool:YES],
                  [NSNumber numberWithBool:YES],
                  [NSNumber numberWithBool:YES],
                  [NSNumber numberWithBool:YES],
                  [NSNumber numberWithBool:YES],
                  [NSNumber numberWithBool:YES],
                  [NSNumber numberWithBool:YES],
                  [NSNumber numberWithBool:YES],
                  [NSNumber numberWithBool:YES],
                  [NSNumber numberWithBool:YES],
                  [NSNumber numberWithBool:YES],
                  [NSNumber numberWithBool:YES],
                  [NSNumber numberWithBool:YES],nil];

    } else {
        self.levels = [defaults objectForKey:@"LEVELS"];
    }

    if ([defaults objectForKey:@"LEVELS_PIZZAS"] == nil) {

        levelsPizzas = [[NSMutableArray alloc] initWithObjects:
                        [NSNumber numberWithInteger:200],
                        [NSNumber numberWithInteger:250],
                        [NSNumber numberWithInteger:300],
                        [NSNumber numberWithInteger:350],
                        [NSNumber numberWithInteger:400],
                        [NSNumber numberWithInteger:450],
                        [NSNumber numberWithInteger:500],
                        [NSNumber numberWithInteger:550],
                        [NSNumber numberWithInteger:600],
                        [NSNumber numberWithInteger:650],
                        [NSNumber numberWithInteger:700],
                        [NSNumber numberWithInteger:800],
                        [NSNumber numberWithInteger:900],
                        [NSNumber numberWithInteger:1000],
                        [NSNumber numberWithInteger:1100],nil];

    } else {

        self.levelsPizzas = [defaults objectForKey:@"LEVELS_PIZZAS"]; 
    }

    if ([defaults objectForKey:@"COLLECTED_PIZZAS"] == nil) {
        self.nCurClickAmounts = 0;
    } else {
        self.nCurClickAmounts = [[defaults objectForKey:@"COLLECTED_PIZZAS"] intValue];
    }
    [defaults synchronize];
}

- (void) saveLevels {
    [[NSUserDefaults standardUserDefaults] setObject:levels forKey:@"LEVELS"];
    [[NSUserDefaults standardUserDefaults] setObject:levelsPizzas forKey:@"LEVELS_PIZZAS"];
    [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInt:nCurClickAmounts] forKey:@"COLLECTED_PIZZAS"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

      

+3


source to share


3 answers


Here's the problematic line:

self.levels = [defaults objectForKey:@"LEVELS"];

      

This gives an immutable NSArray

, even if you kept the modified default array. You need to call mutableCopy

if you want your array to remain mutable:



self.levels = [[defaults objectForKey:@"LEVELS"] mutableCopy];

      

The same goes for an array levelsPizzas

.

+3


source


The values ​​returned from NSUserDefaults are immutable even if you set the mutable object as a value. You must change them.



self.levels = [[defaults objectForKey:@"LEVELS"] mutableCopy];

      

+1


source


The problem is what you think of NSUserDefaults

as different NSDictionary

.

After rebooting the app or syncing the repository, NSUserDefaults may have changed all of your mutable objects (for example NSMutableArray

) to NSArray

.

Any classes stored in NSUserDefaults

that are not NSNumber,NSString,NSDate,NSArray

or are also persisted NSDictionary

.

So don't forget to save them to NSUserDefaults or handle the case when this change happens.

In your code, you just need to change:

self.levels = [defaults objectForKey:@"LEVELS"];

      

to

self.levels = [[defaults objectForKey:@"LEVELS"] mutableCopy];

      

+1


source







All Articles