0

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.

Augustus S-R
  • 67
  • 1
  • 8
  • Can you show us how you implement the NSCoding protocol in `FlipHighScore`, or whatever the class name is? – mbm29414 Apr 29 '12 at 21:10
  • Try this `NSLog(@"unarchived class %@",NSStringFromClass([allHighScores class]));` and note the output. – CodaFi Apr 29 '12 at 21:12
  • 1
    When one object magically transforms into another, this is a sign that the first object was over-released (or under-retained) and got deallocated, and another object was alloc'd in its place. – Lily Ballard Apr 29 '12 at 21:14
  • Yes, I am using ARC. - I will add the coding in a little bit! I put the log in before and after allHighScores is unarchived - before I get "unarchived class null" and after I get "unarchived class CA Layer." – Augustus S-R Apr 29 '12 at 21:26
  • I had thought it might be some sort of memory management issue, but haven't been able to figure out how to fix it. The retain that you suggested didn't seem to work. – Augustus S-R Apr 29 '12 at 21:27
  • Can you upload the file you're unarchiving? – MrMage Apr 29 '12 at 22:25
  • @MrMage - I haven't been able to find it on my computer recently - I saw it once when it was working, but haven't seen it since. – Augustus S-R Apr 29 '12 at 22:32
  • 1
    Why are you using `[super alloc]` instead of `[self alloc]` in `+ sharedStore`? – v1Axvw Apr 30 '12 at 08:51
  • Augustus: you seriously can't find the file you want to unarchive anymore? – MrMage Apr 30 '12 at 09:32
  • @MrMage - I did find the file - the app only crashes after that is created. And the weird thing is, it crashes when it tries to save again. The app works fine if highscores.archive isn't there. – Augustus S-R May 01 '12 at 14:01

2 Answers2

2

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])) {
Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • Thank you! I added the retain and there was no change to the behavior. I didn't think I needed the retain since I was using ARC. – Augustus S-R Apr 29 '12 at 21:25
  • @AugustusS-R: Ah, I didn't realize you were using ARC. You still need the `-mutableCopy` though if you want a mutable array. – Lily Ballard Apr 29 '12 at 21:29
  • Thank you for the thought, Kevin! allHighScores still becomes a CALayer (which obviously is a bad thing...) – Augustus S-R Apr 29 '12 at 21:33
  • 1
    @AugustusS-R If you added `retain` and didn't get an error, you're not using ARC. The compiler will reject any normal attempt to send `retain` if ARC is enabled. – rob mayoff Apr 30 '12 at 04:31
  • @KevinBallard You're not returning `self` (I apparently got down voted for that mistake) and you still haven't provided a mechanism for him to actually `retain` his NSMutableArray. – mbm29414 Apr 30 '12 at 11:57
  • @mbm30075: Oops, transcription error for not returning self, thanks. But the array *is* being retained, by virtue of the `-mutableCopy` call. – Lily Ballard Apr 30 '12 at 18:49
  • My abject apologies - it turns out that I am not using ARC for this project. And adding the retain that @KevinBallard suggested seems to have solved the issue! My apologies again for being a N00B. (We all were once...) And thank you rob mayoff for your comment - because that caused me to research the whole ARC issue. – Augustus S-R May 01 '12 at 14:19
-3

Have you tried this?

-(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];
        }
        // All-important new line....
        [self setAllHighScores:allHighScores];
    }
    return self;
}

Edit/Update: So, here's two versions of what I actually intended in the above example (I'm assuming here that his ivar allHighScores has a corresponding property):

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

This is the way I'd actually do it:

-(id)init { 
    if ((self = [super init])) {
        NSMutableArray *arr = [[NSKeyedUnarchiver unarchiveObjectWithFile:[self flipScoreArchivePath]] mutableCopy];
        if (!arr) arr = [[NSMutableArray alloc] init];
        [self setAllHighScores:arr];
    }
    return self;
}
mbm29414
  • 11,558
  • 6
  • 56
  • 87
  • 2
    Cargo-cult programming at its finest. Assigning to an ivar, and then calling the same setter? That makes no sense. – Lily Ballard Apr 30 '12 at 04:24
  • return self; is missing, too. – MrMage Apr 30 '12 at 09:32
  • Unless, of course, since he's using ARC, the object isn't being retained by simply being assigned to the ivar. He has a weird problem. I wanted to make sure we checked all the basics. Thanks to everyone who down voted an honest attempt at help!!! Do you feel better about yourselves? – mbm29414 Apr 30 '12 at 11:00
  • @KevinBallard So, when I make a mistake (since I never assign directly to ivars, I read a declaration that wasn't there), it's cargo-cult programming, but when you miss a `return self`, it's just an "oops"? Sounds like a double standard to me. Maybe you could be a little bit more charitable until you know someone doesn't know what they're discussing. – mbm29414 May 02 '12 at 02:17
  • @mbm30075: Your "mistake" was throwing random bits of code together in the hopes that it would work, without a deeper understanding of what's going on. That's pretty much the dictionary definition of cargo-cult programming. – Lily Ballard May 02 '12 at 04:24