I have a problem. There is class to store game progress:
struct GameData {
PackData & packDataById(LEVEL_PACK packId);
int gameVersion;
AudioData audio;
PackData sunrise;
PackData monochrome;
PackData nature;
};
//singleton
@interface GameDataObject : NSObject <NSCoding>
{
GameData data_;
}
+(GameDataObject*) sharedObject;
-(id) initForFirstLaunch;
-(GameData*) data;
-(void) save;
@end
and implementation:
@implementation GameDataObject
static GameDataObject *_sharedDataObject = nil;
+ (GameDataObject*) sharedObject
{
if (!_sharedDataObject) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *encodedObject = [defaults objectForKey:Key];
if (!encodedObject) {
_sharedDataObject = [[GameDataObject alloc] initForFirstLaunch];
}
else {
_sharedDataObject = (GameDataObject*)[NSKeyedUnarchiver unarchiveObjectWithData: encodedObject];
}
}
return _sharedDataObject;
}
-(GameData*) data
{
return &data_;
}
-(id) initForFirstLaunch
{
self = [super init];
if (self) {
data_.audio.reset();
data_.sunrise.reset();
data_.monochrome.reset();
data_.nature.reset();
data_.gameVersion = 1;
data_.sunrise.levelData[0].state = LEVEL_OPENED;
}
return self;
}
-(void) save
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[NSKeyedArchiver archivedDataWithRootObject:self] forKey:Key];
[defaults synchronize];
}
-(void) encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeInt:data_.gameVersion forKey:@"game-version"];
[encoder encodeBytes:(uint8_t*)&data_.audio length:sizeof(AudioData) forKey:@"audio-data"];
[encoder encodeBytes:(uint8_t*)&data_.sunrise length:sizeof(PackData) forKey:@"sunrise-pack"];
[encoder encodeBytes:(uint8_t*)&data_.monochrome length:sizeof(PackData) forKey:@"monochrome-pack"];
[encoder encodeBytes:(uint8_t*)&data_.nature length:sizeof(PackData) forKey:@"nature-pack"];
}
-(id) initWithCoder:(NSCoder *)decoder
{
self = [super init];
if (self) {
data_.gameVersion = [decoder decodeIntForKey:@"game-version"];
NSUInteger length = 0;
{
const uint8_t *buffer = [decoder decodeBytesForKey:@"audio-data" returnedLength:&length];
assert(length);
memcpy(&data_.audio, buffer, length);
}
{
const uint8_t *buffer = [decoder decodeBytesForKey:@"sunrise-pack" returnedLength:&length];
assert(length);
memcpy(&data_.sunrise, buffer, length);
}
{
const uint8_t *buffer = [decoder decodeBytesForKey:@"monochrome-pack" returnedLength:&length];
assert(length);
memcpy(&data_.monochrome, buffer, length);
}
{
const uint8_t *buffer = [decoder decodeBytesForKey:@"nature-pack" returnedLength:&length];
assert(length);
memcpy(&data_.nature, buffer, length);
}
}
return self;
}
@end
It loads and saves itself correctly when save is called directly after initialization and nothing more is done.
But when I try a simple thing. I write in appDidFinishLaunching
GameDataObject *obj = [GameDataObject sharedObject];
Then everything is done – just one simple menu is loaded, and I minimize the application so
-(void) applicationDidEnterBackground:(UIApplication*)application
{
[[CCDirector sharedDirector] stopAnimation];
[[GameDataObject sharedObject] save];
}
is executed. And in this method obj is totally corrupted (before saving), sometimes it’s even seen with debugger as another class object.
What am I doing wrong?
EDIT
Just launching the app and minimizing it causes the same problem.
As I already mentioned in the comment, you have a memory bug when unarchiving the data using
NSKeyedUnarchiver.The method
+[NSKeyedUnarchiver unarchiveObjectWithData:]returns an autoreleased object (you can tell from the naming convention: it doesn’t contain eithernew,allocorcopy) so you’d have to take ownership of the object by sending it aretainmessage. Now the object won’t be released by the autorelease pool at the end of the runloop.