Init a NSMutableArray in a singelton that saves as a state

Go To StackoverFlow.com

0

Im am using a pattern from the book Learning Cocos2D. I have a GameState class that is a singleton that can be saved as a state. The BOOL soundOn gets init as false the first time the class gest created, but the array levelProgress dosent. I made comments on all the lines on which I have tried to init the array. The class uses a helper that loads and saves the data. When i try 1 or 2 i get "instance variable 'levelProgress' accessed in class method" error.

#import <Foundation/Foundation.h>
#import "cocos2d.h"

@interface GameState : NSObject <NSCoding> {
    BOOL soundOn;
    NSMutableArray *levelProgress; 
}

+ (GameState *) sharedInstance;
- (void)save;

@property (assign) BOOL soundOn;
@property (nonatomic, retain) NSMutableArray *levelProgress;
@end


#import "GameState.h"
#import "GCDatabase.h"

@implementation GameState

@synthesize soundOn;
@synthesize levelProgress;

static GameState *sharedInstance = nil;

+(GameState*)sharedInstance {
    @synchronized([GameState class]) 
    {
        if(!sharedInstance) {
            sharedInstance = [loadData(@"GameState") retain];
            if (!sharedInstance) {
                [[self alloc] init]; 
                // 1. levelProgress = [[NSMutableArray alloc] init];
            }
        }
        return sharedInstance; 
    }
    return nil; 
}

+(id)alloc 
{
    @synchronized ([GameState class])
    {        
        NSAssert(sharedInstance == nil, @"Attempted to allocate a \
                 second instance of the GameState singleton"); 
        sharedInstance = [super alloc];
        // 2. levelProgress = [[NSMutableArray alloc] init];
        return sharedInstance; 
    }
    return nil;  
}

- (void)save {
    saveData(self, @"GameState");
}

- (void)encodeWithCoder:(NSCoder *)encoder {
    // 3. levelProgress = [[NSMutableArray alloc] init];
    [encoder encodeBool:currentChoosenCountry forKey:@"soundOn"];
    [encoder encodeObject:levelProgress forKey:@"levelProgress"];
}

- (id)initWithCoder:(NSCoder *)decoder {
    if ((self = [super init])) {
        // 4. levelProgress = [[NSMutableArray alloc] init];    
        soundOn = [decoder decodeBoolForKey:@"soundOn"];
        levelProgress = [decoder decodeObjectForKey:@"levelProgress"];
    }
    return self;
}
@end

Solution: *I just added av init method...*

-(id)init {
    self = [super init];
    if (self != nil) {
        levelProgress = [[NSMutableArray alloc] init];
    }
    return self;
}
2012-04-04 07:57
by Johan Albertsson


1

Try this (this code may contain syntax or logic errors - I wrote it in notepad):

@interface GameState : NSObject <NSCoding>

@property (nonatomic, readwrite) BOOL soundOn;
@property (nonatomic, retain) NSMutableArray *levelProgress;

+ (GameState *)sharedState;
- (void)writeDataToCache;

@end

//

@implementation GameState

@synthesize soundOn, levelProgress;

#pragma mark - Singleton

static GameState *sharedState = nil;

+ (void)initialize {
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        if ([[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:@"%@/gameState", pathCache]]) {
            NSData *encodedObject = [[NSData alloc] initWithContentsOfFile:[NSString stringWithFormat:@"%@/gameState", pathCache]];
            data = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
        }
        else
            data = [[GameState alloc] init];
    }
}
+ (GameState *)sharedState {
    return sharedState;
}

#pragma mark - Initialization

- (id)init {
    if (self = [super init]) { // will be inited while application first run
        soundOn = NO;
        levelProgress = [[NSMutableArray alloc] init];

        return self;
    }
    return nil;
}

#pragma mark - Coding Implementation

- (void)writeDataToCache { // use this method to save current state to cache
    NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:self];
    if([[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:@"%@/GameState", pathCache]])
        [[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:@"%@/GameState", pathCache] error:nil];
    [[NSFileManager defaultManager] createFileAtPath:[NSString stringWithFormat:@"%@/GameState", pathCache] contents:encodedObject attributes:nil];
    NSLog(@"GameState was saved successfully.");
}
- (void)encodeWithCoder:(NSCoder*)encoder {
    [encoder encodeBool:self.soundOn forKey:@"soundOn"];
    [encoder encodeObject:self.levelProgress forKey:@"levelProgress"];
}
- (id)initWithCoder:(NSCoder*)decoder {
    if ((self = [super init])) {
        self.soundOn = [decoder decodeBoolForKey:@"soundOn"];
        self.levelProgress = [decoder decodeObjectForKey:@"levelProgress"];

        NSLog(@"GameState was inited successfully");
        return self;
    }
    return nil;
}

@end
2012-04-04 08:16
by demon9733
Does this change replace the class with load and save? The pathCache is missing. Is it just a string - Johan Albertsson 2012-04-04 08:43
Didn't understand first question. pathCache is just a string to your application cache folder - demon9733 2012-04-04 08:46


0

1 & 2 are the same (and should not work, since levelProgress is an instance variable), 3 and 4 should be encoding/decoding the array, not initializing it anew.

2012-04-04 08:01
by Alexander
But when i try them i get "instance variable 'levelProgress' accessed in class method. Error - Johan Albertsson 2012-04-04 08:05
My bad. Your class methods (+) can't access instance variables (they don't exist - Alexander 2012-04-04 08:07
But the code compiles but the array gets 0X0 when i watch it in the debugger - Johan Albertsson 2012-04-04 08:07
You should implement the -(id)init method and initialize your array there. You're already calling it in +(GameState *)sharedInstance - Alexander 2012-04-04 08:10
I added a init but when i put a debugger break in it the code wont reach it. I tried to change the name on the save file name to - Johan Albertsson 2012-04-04 08:15
-(id)init {
self = [super init]; if (self != nil) { levelProgress = [[NSMutableArray alloc] init]; } return self; - Johan Albertsson 2012-04-04 08:18
Looking at the code, move the initialization of the array in the loadData function - Alexander 2012-04-04 08:18


0

1 and 2 are a mistake - you can't access instance variable without specifying an instance.

You can fix 1 by changing the line to sharedInstance.levelProgress = ....

2 is just a bad idea, you're breaking common convention of initializing using init... methods, it may surprise and cause problems for an other programmer who'll be working on the same code later.

3 and 4 are ok but if loadData fails they will not be executed and the object will be initialized with ordinary init and the array will be nil pointer:

[[self alloc] init];

you should also override init and initialize the properties there.

2012-04-04 08:17
by hamstergene
Ads