使用 NSData 和 NSMutableArrays 保存 OS X 游戏数据

Saving OS X Game Data Using NSData and NSMutableArrays

我正在尝试将几个级别的高分存储在 NSMutableArray 中,然后将其保存在 Documents 文件夹中的文件中。我知道我可以使用 plists,但我不希望内容被用户修改。 NSMutableArray *hiscore 似乎没有被初始化,我不确定如何解决这个问题。对于基元来说它似乎很好,但对于对象它不起作用。

GameData.h

@interface GameData : NSObject <NSCoding>

@property (assign, nonatomic) int level;
@property (assign, nonatomic) NSMutableArray *hiscore;

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

@end

GameData.m

#import "GameData.h"

@implementation GameData

static NSString* const GameDataLevelKey = @"level";
static NSString* const GameDataHiscoreKey = @"hiscore";

- (instancetype)initWithCoder:(NSCoder *)decoder {
    self = [self init];
    if (self) {
        _level = [decoder decodeDoubleForKey: GameDataLevelKey];
        _hiscore = [decoder decodeObjectForKey:GameDataHiscoreKey];
    }
    return self;
}

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

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

    return sharedInstance;
}

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

    return [[GameData alloc] init];
}

-(void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeDouble:self.level forKey: GameDataLevelKey];
    [encoder encodeObject:self.hiscore forKey:GameDataHiscoreKey];
}

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

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

-(void)reset {
    self.level = 0;
}

@end

LevelScene.m

#import "GameData.h"
...
[[[GameData sharedGameData] hiscore] addObject:@1500];
[[GameData sharedGameData] save];

几件事:

首先,在 initWithCoder: 中,如果还没有游戏文件(如首次启动时的情况,或者保存时没有得分),_hiscore 将结束 nil,因此您将无法为其添加任何分数。我会在调用 decodeObjectForKey: 后检查它是否为 nil,如果为 nil,则将其初始化为一个空数组。

- (instancetype)initWithCoder:(NSCoder *)decoder {
    self = [self init];
    if (self) {
        _level = [decoder decodeDoubleForKey: GameDataLevelKey];
        _hiscore = [decoder decodeObjectForKey:GameDataHiscoreKey];
        if( _hiscore == nil ) {
            _hiscore = [NSMutableArray array];
        }
    }
    return self;
}

其次,GameData 拥有 _hiscore,因此需要对其进行强引用。

@property (strong, nonatomic) NSMutableArray *hiscore;

编辑: 一般规则(参见Memory Management section of Apple's Cocoa Core Competencies Guide)是:

You own any object you create by allocating memory for it or copying it.

这通常意味着您使用包含 alloccopy 的方法创建的任何对象,但它也属于便利方法,例如 array 等。请注意 [NSMutableArray array] 完全等同于 [[NSMutableArray alloc] init];使用前者的唯一原因是它不需要打字(很多人,包括我自己,都是它的粉丝)。

然后,

If you own an object, either by creating it or expressing an ownership interest, you are responsible for releasing it when you no longer need it.

ARC 之前,这意味着在其上调用 releaseautorelease

在 ARC 世界中,内存管理主要 为您处理,但您仍然需要稍微了解其背后的概念才能理解。基本上,如果您创建一个对象(使用包含 alloccopy 的方法,或 array 等便捷方法),那么您就拥有该对象。您通过创建对该对象的 strong 引用来表达对该对象的所有权。如果你没有这样做(比如,你的引用是 weakassign),那么 ARC 将(正确地)假设你已经完成了那个对象,并会在它决定你时立即释放它完成它。在上面的代码中,可以肯定的是,一旦 initWithCoder 返回,ARC 选择释放 _hiscore。稍后,您的代码尝试访问 _hiscore,但它已被释放,因此您崩溃了。

没有代码初始化 _highscore,它没有崩溃。因为 _highscore 始终是 nil,并且在 Objective-C 中向 nil 发送消息完全没问题;什么都没发生。但是如果你真的初始化它,并且只将它设置为 assign,那么它就不再是 nil。一旦 ARC 释放它,_highscore 指向一个不再存在的对象,这就是导致崩溃的原因。