2016-03-04 4 views
1

Я пытаюсь сохранить массив пользовательских спрайтов. Вот мой класс для этого:Сохранение массива пользовательских SKSpriteNodes вызывает BAD ACCESS

SaveData.h

#import <Foundation/Foundation.h> 

@interface SaveData : NSObject <NSCoding> 

//persistent data 
@property (assign, nonatomic) NSMutableArray *treeData; 

+(instancetype)sharedSaveData; 
-(void)save; 

@end 

SaveData.m

#import "SaveData.h" 

@interface SaveData() 

@end 

@implementation SaveData 

static NSString * const SaveDataTreeKey = @"treedata"; 

-(instancetype) initWithCoder:(NSCoder *)decoder { 
    self = [self init]; 
    if (self) { 
     _treeData = [decoder decodeObjectForKey:SaveDataTreeKey]; 

     if (_treeData == nil) { 
      _treeData = [NSMutableArray array]; 
     } 
    } 
    return self; 
} 

+(instancetype) sharedSaveData { 
    static id sharedInstance = nil; 

    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     sharedInstance = [self loadInstance]; 
    }); 

    return sharedInstance; 
} 

+(instancetype) loadInstance { 
    NSData* decodedData = [NSData dataWithContentsOfFile: [SaveData filePath]]; 
    if (decodedData) { 
     SaveData* saveData = [NSKeyedUnarchiver unarchiveObjectWithData:decodedData]; 
     return saveData; 
    } 

    SaveData *data = [[SaveData alloc] init]; 

    if (data.treeData == nil) 
     data.treeData = [NSMutableArray array]; 

    return data; 
} 

-(void) encodeWithCoder:(NSCoder *)encoder { 
    [encoder encodeObject:self.treeData forKey:SaveDataTreeKey]; 
} 

+(NSString*) filePath { 
    static NSString* filePath = nil; 
    if (!filePath) { 
     filePath = 
     [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] 
     stringByAppendingPathComponent:@"savedata"]; 
    } 
    return filePath; 
} 

-(void) save { 
    NSData* encodedData = [NSKeyedArchiver archivedDataWithRootObject: self]; 
    [encodedData writeToFile:[SaveData filePath] atomically:YES]; 
} 

@end 

Я использовал этот код, прежде чем в другом проекте, но я понял, что Я сохранял только типы объектов и примитивов, которые допускает NSCoding (NSNumber, NSDictionary и т. Д.). Я пытаюсь сохранить массив SKSpriteNodes (подклассы).

Подкласс SKSpriteNode имеет пару логических элементов и, конечно же, хранит текстуру, ее размер и местоположение на экране. Вот что я писал для методов NSCoding для моего подкласса под названием Производитель:

"Producer.m"

- (id)initWithCoder:(NSCoder *)decoder { 
    if (self = [super init]) { 
     self.isEliminated = [decoder decodeObjectForKey:@"isEliminated"]; 
     self.isPlaced = [decoder decodeObjectForKey:@"isPlaced"]; 
     self.isSelected = [decoder decodeBoolForKey:@"isSeelcted"]; 
     self.isVisible = [decoder decodeBoolForKey:@"isVisible"]; 

     int x = [decoder decodeIntForKey:@"x"]; 
     int y = [decoder decodeIntForKey:@"y"]; 
     self.position = CGPointMake(x, y); 

     [self createSelectionBox]; 
    } 
    return self; 
} 

- (void)encodeWithCoder:(NSCoder *)encoder { 
    [encoder encodeBool:_isSelected forKey:@"isSelected"]; 
    [encoder encodeBool:_isEliminated forKey:@"isEliminated"]; 
    [encoder encodeBool:_isPlaced forKey:@"isPlaced"]; 
    [encoder encodeBool:_isVisible forKey:@"isVisible"]; 
    [encoder encodeInt:self.position.x forKey:@"x"]; 
    [encoder encodeInt:self.position.y forKey:@"y"]; 
} 

"Producer.h"

#import <SpriteKit/SpriteKit.h> 

@interface Producer : SKSpriteNode <NSCoding> 

@property bool isEliminated; 
@property bool isPlaced; 
@property bool isSelected; 
@property bool isVisible; 

-(instancetype) initWithInvisibleState; 
-(instancetype) initWithImageNamed:(NSString *)fileName; 
-(instancetype) initWithImageFromBundleNamed:(NSString*)fileName; 

-(void) select; 
-(void) deselect; 

@end 

В моей первой сцене , объекты-производители создаются, а затем сохраняются в массиве. Я пытаюсь сохранить этот массив путем кодирования с помощью моего класса SaveData.m. Вот вызов (с первой сцены):

-(void) willMoveFromView:(SKView *)view { 
    [SaveData sharedSaveData].treeData = _tree.data; 
    [[SaveData sharedSaveData] save]; 
    [self removeAllChildren]; 
} 

_tree.data - массив, который я сохраняю.

В приложение переходит в новую сцену, а затем я пытаюсь извлечь данные из сохраненного файла еще раз, используя класс SaveData по телефону следующее:

NSLog(@"%@", [SaveData sharedSaveData].treeData); //print out data in array 

Я не уверен, что я делая ошибку, но я получаю следующую ошибку: Thread 1:EXC_BAD_ACCESS (code=EXC_I386_GPFLT). Есть идеи?

+0

Вы добавили точку останова и знаете, какая строка точно выбрасывает ошибку? –

+0

Да, я сделал. Он показывает, что проблема с деревом SaveData treeData является проблемой. Я попытался использовать отладчик, чтобы углубиться в этот вызов, но он ничего не раскрывает. – 02fentym

+0

Я следил за учебником Ray Wenderlich по кодированию игровых данных, и он отлично работал для меня в другом проекте. В любом случае, я скопировал свою хорошую рабочую версию и внес некоторые изменения, и она отлично работает. Я честно не вижу, где проблема была в коде, который я опубликовал. Я уверен, что это небольшая ошибка. – 02fentym

ответ

0

Ненавижу отвечать на свой вопрос, но я решил создать еще один проект, чтобы узнать, было ли это в том, как я кодировал SKSpriteNode. Оказывается, что-то не так с моим классом SaveData.

Примечание: Я использовал Ray Wenderlich's solution, чтобы создать этот класс и изменить его для моих собственных целей.

Я не мог понять, какая строка кода была отключена, главным образом потому, что на самом деле я действительно не придумал код, и у меня есть только частичное понимание того, как все это работает (я получаю суть его).

Вот что выглядит рабочая версия SaveData на всякий случай, когда это помогает кому-либо еще в будущем. Возможно, кто-то может сказать мне, что расхождение между сломанным (вверху) и этой версией.

SaveData.h

#import <Foundation/Foundation.h> 

@interface SaveData : NSObject <NSCoding> 

//persistent data 
@property (strong, nonatomic) NSMutableArray *treeData; 

+(instancetype)sharedSaveData; 
-(void)save; 
-(void)reset; 

@end 

SaveData.м

#import "SaveData.h" 

@interface SaveData() 


@end 

@implementation SaveData 


static NSString * const SaveDataTreeDataKey = @"treedata"; 

-(instancetype) initWithCoder:(NSCoder *)decoder { 
    self = [self init]; 
    if (self) { 
     _treeData = [decoder decodeObjectForKey:SaveDataTreeDataKey]; 

     if (_treeData == nil) 
      _treeData = [NSMutableArray array]; 
    } 
    return self; 
} 

+(instancetype) sharedSaveData { 
    static id sharedInstance = nil; 

    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     sharedInstance = [self loadInstance]; 
    }); 

    return sharedInstance; 
} 

+(instancetype) loadInstance { 
    NSData* decodedData = [NSData dataWithContentsOfFile: [SaveData filePath]]; 
    if (decodedData) { 
     SaveData* saveData = [NSKeyedUnarchiver unarchiveObjectWithData:decodedData]; 
     return saveData; 
    } 

    //this happens if the game save file doesn't exist (first run of game) 
    SaveData *data = [[SaveData alloc] init]; 

    if (data.treeData == nil) { 
     data.treeData = [NSMutableArray array]; 
    } 
    return data; 
} 


-(void) encodeWithCoder:(NSCoder *)encoder { 
    [encoder encodeObject:self.treeData forKey:SaveDataTreeDataKey]; 
} 

+(NSString*) filePath { 
    static NSString* filePath = nil; 
    if (!filePath) { 
     filePath = 
     [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] 
     stringByAppendingPathComponent:@"gamedata"]; //saves in the Documents folder (might have to change this later) 
    } 
    return filePath; 
} 

-(void) save { 
    NSData* encodedData = [NSKeyedArchiver archivedDataWithRootObject: self]; 
    [encodedData writeToFile:[SaveData filePath] atomically:YES]; 
} 

-(void) reset { 
    _treeData = [NSMutableArray array]; 
    [self save]; 
} 

@end 

Еще одна вещь. Для кодирования SKSpriteNode, я создал два метода в моем SKSpriteNode подкласса следующим образом:

От SKSpriteNode .m файл

- (id)initWithCoder:(NSCoder *)decoder { 
    if (self = [super init]) { 
     self.name = [decoder decodeObjectForKey:@"name"]; 

     NSValue *decodedValue = [decoder decodeObjectForKey:@"position"]; 
     CGPoint point; 
     [decodedValue getValue:&point]; 
     self.position = point; 
    } 
    return self; 
} 

- (void)encodeWithCoder:(NSCoder *)encoder { 
    [encoder encodeObject:self.name forKey:@"name"]; 

    CGPoint point = self.position; 
    NSValue *pointValue = [NSValue value:&point withObjCType:@encode(CGPoint)]; 
    [encoder encodeObject:pointValue forKey:@"position"]; 
} 

Приведенный выше код кодирует имя спрайта и его позиции. Просто закодируйте все переменные экземпляра, необходимые для сохранения объекта.