Search code examples
iosnskeyedarchiver

My NSMutableArray suddenly becomes a CALayer


I think I am hallucinating. I am trying to add some persistence to my Concentration-lke game. I would like to keep track of high scores. I got this partially working for a little while today and now it has all gone kablooie (I think that is the correct iOS terminology). Now, my allHighScores NSMutablearray suddenly becomes a CALayer. I am using NSKeyed Archiving. I have a break point in my file before allHighScores gets loaded with data. When stepping through the application, allHighScores exists as an NSMutableArray - then, at the next step, it suddenly becomes a CA Layer. Huh?

-(id)init 
{
    self = [super init];
    if (self) {
        NSString *path = [self flipScoreArchivePath];
        NSLog(@"Path is %@", path);
        allHighScores = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
        if (!allHighScores)  {
        allHighScores = [[NSMutableArray alloc] init];
        }
    }
    return self;
}


+(FlipHighScoreStore *)sharedStore {
    static FlipHighScoreStore *sharedStore = nil;
    if (!sharedStore) {
        sharedStore = [[super allocWithZone:nil]init];
    }
    return sharedStore;
}

Somehow, calling NSKeyedUnarchiver changes my allHighScores from an NSMutableArray into a CALayer. I am very confused.

I tried adding a retain to the unarchiving instruction, but that didn't help.

Here is my encoding/decoding code:

-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.themeChosen forKey:@"themeChosen"];
[aCoder encodeInt:self.highScore forKey:@"highScore"];
[aCoder encodeInt:self.scoreStartLevel forKey:@"scoreStartLevel"];
[aCoder encodeInt:self.scoreFinishLevel forKey:@"scoreFinishLevel"];
[aCoder encodeObject:scoreDateCreated forKey:@"scoreDateCreated"];}

-(id)initWithCoder:(NSCoder *)aDecoder {
if (self) {
    self.themeChosen = [aDecoder decodeObjectForKey:@"themeChosen"];
    self.highScore = [aDecoder decodeIntForKey:@"highScore"];
    self.scoreStartLevel = [aDecoder decodeIntForKey:@"scoreStartLevel"];
    self.scoreFinishLevel = [aDecoder decodeIntForKey:@"scoreFinishLevel"];
    scoreDateCreated = [aDecoder decodeObjectForKey:@"scoreDateCreated"];
}
return self;}

UPDATE: The program crashes when a "highscores.archive" file already exists and a save is called again. I can launch the app, look at the high scores - they are there and retrieved happily, but the save code:

-(BOOL)saveHighScores {
NSString *path = [self flipScoreArchivePath];
return [NSKeyedArchiver archiveRootObject:allHighScores toFile:path];}

causes a EXC_BAD_ACCESS. The path is right, so somehow the allHighScores isn't.


Solution

  • The problem here is you aren't retaining the results of the unarchiving. According to the Basic Memory Management Rules, a method by the name of +unarchiveObjectWithFile: will return an autoreleased object. As such, since you are placing it into an ivar, you need to retain this object, or it will get deallocated out from under you.

    Although in your case, since you want a mutable array, you actually need to call -mutableCopy since NSKeyedUnarchive will just give you an immutable array.

    -(id)init {
        if ((self = [super init])) {
            NSString *path = [self flipScoreArchivePath];
            NSLog(@"Path is %@", path);
            allHighScores = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] mutableCopy];
            if (!allHighScores) {
                allHighScores = [[NSMutableArray alloc] init];
            }
        }
        return self;
    }
    

    Your -initWithCoder: isn't calling super. You need to say

    if ((self = [super initWithCoder:aDecoder])) {