发送到不可变对象的变异方法关闭应用程序
Mutating method sent to immutable object closes app
在我的代码中,当用户完成一个级别时,它会解锁下一个级别(总共 15 个级别)。
但是,我注意到,当我第一次完成关卡时,它可以运行,但是,当我返回并再次尝试时,它会崩溃并出现此错误;
[__NSCFArray replaceObjectAtIndex:withObject:]: mutating method sent to immutable object
代码部分是这样的;
//Unlock Next Level
if (levelNumber != 15) {
[m_appDelegate.levels replaceObjectAtIndex:levelNumber withObject:[NSNumber numberWithBool:NO]];
[m_appDelegate saveLevels];
}
如果我删除这一行;
[m_appDelegate.levels replaceObjectAtIndex:levelNumber withObject:[NSNumber numberWithBool:NO]];
然后应用程序不会崩溃,当然,它不会解锁任何其他级别。
下面的一些参考资料可能会有所帮助;
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];
}
在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];
}
这是有问题的行:
self.levels = [defaults objectForKey:@"LEVELS"];
这为您提供了一个不可变的 NSArray
,即使您将可变数组保存为默认值也是如此。如果您希望数组保持可变,则需要调用 mutableCopy
:
self.levels = [[defaults objectForKey:@"LEVELS"] mutableCopy];
levelsPizzas
数组也是如此。
从 NSUserDefaults 返回的值是不可变的,即使您将可变对象设置为值也是如此。你必须可变复制它们。
self.levels = [[defaults objectForKey:@"LEVELS"] mutableCopy];
问题是您认为 NSUserDefaults
只是另一个 NSDictionary
。
重新启动应用程序或存储同步后,NSUserDefaults 可能已将所有可变对象(例如 NSMutableArray
)更改为 NSArray
。
此外,您存储在 NSUserDefaults
中的任何 NSNumber,NSString,NSDate,NSArray
或 NSDictionary
以外的 类 都将消失。
因此请确保不要将它们存储在 NSUserDefaults 中或在发生此更改时处理这种情况。
在您的代码中,您只需更改:
self.levels = [defaults objectForKey:@"LEVELS"];
到
self.levels = [[defaults objectForKey:@"LEVELS"] mutableCopy];
在我的代码中,当用户完成一个级别时,它会解锁下一个级别(总共 15 个级别)。
但是,我注意到,当我第一次完成关卡时,它可以运行,但是,当我返回并再次尝试时,它会崩溃并出现此错误;
[__NSCFArray replaceObjectAtIndex:withObject:]: mutating method sent to immutable object
代码部分是这样的;
//Unlock Next Level
if (levelNumber != 15) {
[m_appDelegate.levels replaceObjectAtIndex:levelNumber withObject:[NSNumber numberWithBool:NO]];
[m_appDelegate saveLevels];
}
如果我删除这一行;
[m_appDelegate.levels replaceObjectAtIndex:levelNumber withObject:[NSNumber numberWithBool:NO]];
然后应用程序不会崩溃,当然,它不会解锁任何其他级别。
下面的一些参考资料可能会有所帮助;
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];
}
在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];
}
这是有问题的行:
self.levels = [defaults objectForKey:@"LEVELS"];
这为您提供了一个不可变的 NSArray
,即使您将可变数组保存为默认值也是如此。如果您希望数组保持可变,则需要调用 mutableCopy
:
self.levels = [[defaults objectForKey:@"LEVELS"] mutableCopy];
levelsPizzas
数组也是如此。
从 NSUserDefaults 返回的值是不可变的,即使您将可变对象设置为值也是如此。你必须可变复制它们。
self.levels = [[defaults objectForKey:@"LEVELS"] mutableCopy];
问题是您认为 NSUserDefaults
只是另一个 NSDictionary
。
重新启动应用程序或存储同步后,NSUserDefaults 可能已将所有可变对象(例如 NSMutableArray
)更改为 NSArray
。
此外,您存储在 NSUserDefaults
中的任何 NSNumber,NSString,NSDate,NSArray
或 NSDictionary
以外的 类 都将消失。
因此请确保不要将它们存储在 NSUserDefaults 中或在发生此更改时处理这种情况。
在您的代码中,您只需更改:
self.levels = [defaults objectForKey:@"LEVELS"];
到
self.levels = [[defaults objectForKey:@"LEVELS"] mutableCopy];