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;
}
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
pathCache
is just a string to your application cache folder - demon9733 2012-04-04 08:46
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.
-(id)init
method and initialize your array there. You're already calling it in +(GameState *)sharedInstance
- Alexander 2012-04-04 08:10
loadData
function - Alexander 2012-04-04 08:18
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.