Objective-c单例对象无法按预期工作

时间:2011-02-19 23:40:08

标签: iphone objective-c nsuserdefaults

我有问题。有存储游戏进度的类:

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

和实施:

@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

在初始化后直接调用save时,它会正确加载并保存自己,并且不再执行任何操作。

但是当我尝试一件简单的事情时。我写在appDidFinishLaunching

GameDataObject *obj = [GameDataObject sharedObject]; 

然后一切都完成了 - 只加载了一个简单的菜单,我最小化了应用程序

-(void) applicationDidEnterBackground:(UIApplication*)application
{
    [[CCDirector sharedDirector] stopAnimation];
    [[GameDataObject sharedObject] save];
}

已执行。在这种方法中obj完全被破坏(在保存之前),有时甚至可以将调试器视为另一个类对象。

我做错了什么?

修改

只需启动应用程序并将其最小化就会导致同样的问题。

3 个答案:

答案 0 :(得分:2)

虽然您有答案,但您的整体应用架构可能会进行一些改进。

值得注意的是,拥有与任意单例实例化相关的大量持久逻辑通常非常脆弱。它引入了各种奇怪的排序依赖关系或其他机制,通过这些机制看似微小的变化可能导致代码中断。

一个不那么脆弱的模式是将状态重建与应用程序生命周期中的已知点相关联。即如果应用程序需要状态,请在applcationDidFinishLaunching:中加载状态。如果子系统仅需要状态,则在加载子系统时加载它。

这样做可以降低复杂性,从而降低代码的维护成本。你可以消除的任何非决定性都是未来的错误。

答案 1 :(得分:2)

正如我在评论中已经提到的,使用NSKeyedUnarchiver取消归档数据时存在内存错误。

方法+[NSKeyedUnarchiver unarchiveObjectWithData:]返回一个自动释放的对象(您可以从命名约定中看出:它不包含newalloccopy)所以你' d必须通过发送retain消息来获取对象的所有权。现在,在runloop结束时,自动释放池不会释放该对象。

答案 2 :(得分:1)

读取存档的共享对象时,必须在分配单个变量时保留它。用于取消归档的NSCoder方法总是返回自动释放的对象。