2011-02-19 1 views
0

У меня проблема. Существует класс прогресса магазин игры:Объект Objective-c singleton не работает должным образом

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 

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

Но когда я пробую простую вещь. Я пишу в appDidFinishLaunching

GameDataObject *obj = [GameDataObject sharedObject]; 

Тогда все делается - только один простое меню загрузки, и я свести к минимуму применение так

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

выполняется. И в этом методе obj полностью поврежден (перед сохранением), иногда его даже видели с отладчиком в качестве другого объекта класса.

Что я делаю неправильно?

EDIT

Просто запуск приложения и свести к минимуму это вызывает те же проблемы.

+0

В '+ sharedObject' вы не сохраняете' _sharedDataObject', когда он распаковывается с помощью 'NSKeyedUnarchiver'. Это может вызвать проблемы. – Joost

+0

@JoostK: Вы совершенно правы! Я чувствую себя настолько глупо.Пожалуйста, добавьте это как ответ, чтобы я мог закрыть эту тему. – Andrew

ответ

2

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

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

1

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

2

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

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

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

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

+0

GameData - это структура, обеспечивающая прогресс и настройки. Он используется из первого меню и используется во всей игре (в сцене выбора пакета, в сцене выбора уровня, в сцене с уровнем выигрыша). Также он вызывается в коде в applicationDidFinishLaunching, чтобы найти ошибку. На самом деле это будет вызвано сначала необходимо – Andrew

+1

Я бы предложил unarchive в явном виде на 'applicationDidFinishLaunching:' и сохраняя его периодически, если это необходимо. * Магия * через, казалось бы, безобидные методы часто укусит вас по дороге. – bbum