@PhilM thank you! That was what we needed.
Here's the implementation of @Clain's suggestions above. How does it look? Any "best practices" suggestions?
In particular, do I need to have @synchronized() around my saveState method?
Is there a difference between @synchronized([GameState class]) and @synchronized(self)?
Am I retaining and releasing the savePath and currentLevel correctly?
GameState.h:
#import "Import.h"
@interface GameState : NSObject <NSCoding>
{
NSString* currentLevel;
CGFloat health,maxHealth,strength,dexterity;
int swordLevel;
BOOL terminating;
}
@property (copy) NSString* currentLevel;
@property CGFloat health,maxHealth,strength,dexterity;
@property int swordLevel;
@property BOOL terminating;
+(GameState*) get;
+(void) purge;
+(void) loadState;
+(void) saveState;
+(NSString*) makeSavePath;
-(void) clear;
@end
GameState.m:
#import "Import.h"
static GameState* singleton = nil;
@implementation GameState
@synthesize currentLevel,health,maxHealth,strength,dexterity,swordLevel;
+(GameState*) get
{
@synchronized(self)
{
// create our single instance
if(singleton == nil)
singleton = [[self alloc] init];
}
return singleton;
}
+(id) alloc
{
@synchronized(self)
{
// assert that we are the only instance
NSAssert(singleton == nil, @"There can only be one GameState");
return [super alloc];
}
return nil;
}
+(void) purge
{
@synchronized(self)
{
[singleton release];
}
}
+(void) loadState
{
@synchronized(self)
{
// release any existing instance
[singleton release];
// load our data
NSString* savePath = [self makeSavePath];
singleton = [[NSKeyedUnarchiver unarchiveObjectWithFile:savePath] retain];
[savePath release];
// couldn't load?
if(singleton == nil)
{
NSLog(@"Couldn't load game state, so initialized with defaults");
[self get];
}
NSLog(@"Loaded game state %@", singleton);
}
}
+(void) saveState
{
// save our data
NSString* savePath = [self makeSavePath];
[NSKeyedArchiver archiveRootObject:[GameState get] toFile:savePath];
[savePath release];
NSLog(@"Saved game state %@ to file %@", singleton, savePath);
}
+(NSString*) makeSavePath
{
// make save path from our document's directory
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentsDirectory = [paths objectAtIndex:0];
NSString* savePath = [documentsDirectory stringByAppendingPathComponent:@"savefile.sav"];
[savePath retain];
return savePath;
}
-(id) init
{
self = [super init];
if( self != nil )
{
[self clear];
NSLog(@"gameState init %@", self);
}
return self;
}
-(void) dealloc
{
[currentLevel release];
[super dealloc];
}
-(NSString*) description
{
return [NSString stringWithFormat:@"level=%@, health=%.1f, maxHealth=%.1f, strength=%.1f, dexterity=%.1f, swordLevel=%d",
currentLevel, health, maxHealth, strength, dexterity, swordLevel];
}
-(void) clear
{
// erase all data
[currentLevel release];
currentLevel = nil;
health = maxHealth = 100.0f;
strength = 6.0f;
dexterity = 5.0f;
swordLevel = 0;
terminating = NO;
}
-(id) initWithCoder:(NSCoder*)coder
{
self = [super init];
if( self != nil )
{
// decode data
currentLevel = [[coder decodeObjectForKey:@"currentLevel"] retain];
health = [coder decodeFloatForKey:@"health"];
maxHealth = [coder decodeFloatForKey:@"maxHealth"];
strength = [coder decodeFloatForKey:@"strength"];
dexterity = [coder decodeFloatForKey:@"dexterity"];
swordLevel = [coder decodeIntForKey:@"swordLevel"];
terminating = NO;
}
return self;
}
-(void) encodeWithCoder:(NSCoder*)coder
{
// encode data
[coder encodeObject:currentLevel forKey:@"currentLevel"];
[coder encodeFloat:health forKey:@"health"];
[coder encodeFloat:maxHealth forKey:@"maxHealth"];
[coder encodeFloat:strength forKey:@"strength"];
[coder encodeFloat:dexterity forKey:@"dexterity"];
[coder encodeInt:swordLevel forKey:@"swordLevel"];
}
@end