Объект Objective-C не работает должным образом - PullRequest
0 голосов
/ 20 февраля 2011

У меня проблема. Есть класс для хранения прогресса игры:

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 полностью поврежден (перед сохранением), иногда он даже рассматривается с отладчиком как другой объект класса.

Что я делаю не так?

EDIT

Та же проблема возникает при запуске приложения и его сворачивании.

Ответы [ 3 ]

2 голосов
/ 20 февраля 2011

Пока у вас есть ответ, ваша общая архитектура приложения может быть немного доработана.

Примечательно, что обычно довольно хрупко иметь какое-то огромное количество постоянной логики, связанной с созданием произвольного синглтона. Он вводит все виды странных зависимостей упорядочения или других механизмов, с помощью которых кажущееся незначительным изменение может привести к поломке вашего кода.

Гораздо менее хрупкий паттерн - связывать восстановление состояния с известными точками в жизни приложения. То есть если для работы приложения требуется состояние, загрузите его в applcationDidFinishLaunching:. Если состояние требуется только для подсистемы, загрузите его при загрузке подсистемы.

Это снижает сложность и, как следствие, снижает затраты на обслуживание вашего кода. Любой индетерминизм, который вы можете устранить, - это будущая ошибка.

2 голосов
/ 20 февраля 2011

Как я уже упоминал в комментарии, у вас есть ошибка памяти при разархивировании данных с использованием NSKeyedUnarchiver.

Метод +[NSKeyedUnarchiver unarchiveObjectWithData:] возвращает автоматически освобожденный объект (вы можете сказать из соглашения об именах: он не содержит ни new, alloc, ни copy), поэтому вам придется вступить во владение объектом отправив ему сообщение retain. Теперь объект не будет освобожден пулом автоматического выпуска в конце цикла выполнения.

1 голос
/ 20 февраля 2011

При чтении заархивированного общего объекта вы должны сохранить его при присвоении вашей одноэлементной переменной. Методы NSCoder для разархивирования всегда возвращают автоматически освобожденные объекты.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...