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

Posted

技术标签:

【中文标题】使用 NSData 和 NSMutableArrays 保存 OS X 游戏数据【英文标题】:Saving OS X Game Data Using NSData and NSMutableArrays 【发布时间】:2015-06-27 19:01:14 【问题描述】:

我正在尝试将几个级别的高分存储在 NSMutableArray 中,然后将其保存在 Documents 文件夹中的文件中。我知道我可以使用 plist,但我不希望用户修改内容。 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];

【问题讨论】:

【参考方案1】:

几件事:

首先,在initWithCoder: 中,如果已经没有游戏文件(就像第一次启动时的情况,或者如果保存时没有分数),_hiscore 将结束 nil,所以你赢了'无法为其添加任何分数。在调用decodeObjectForKey: 之后,我会检查它是否为零,如果它是零,则将其初始化为一个空数组。

- (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)是:

您拥有通过为它分配内存或复制它创建的任何对象。

这通常意味着您使用包含alloccopy 的方法创建的任何对象,但它也适用于array 等便捷方法。注意[NSMutableArray array] 完全等同于[[NSMutableArray alloc] init];使用前者的唯一原因是它减少了打字(包括我自己在内的许多人都喜欢)。

那么,

如果您拥有一个对象,无论是通过创建它还是表达所有权权益,您都有责任在不再需要它时释放它。

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

在 ARC 世界中,内存管理大部分由您负责,但您仍然需要了解其背后的概念才能使其有意义。基本上,如果您创建一个对象(使用包含alloccopy 的方法,或array 等便利方法),那么您就拥有该对象。您可以通过创建对该对象的strong 引用来表达该对象的所有权。如果您没有这样做(例如,您的参考是weakassign),那么 ARC 将(正确地)假设您已完成该对象,并在确定您完成后立即释放它它。在上面的代码中,ARC 选择在initWithCoder 返回后立即释放_hiscore 是一个安全的赌注。后来,你的代码尝试访问_hiscore,但它已经被释放,所以你崩溃了。

没有代码初始化_highscore,它没有崩溃。因为_highscore 总是nil,在Objective-C 中向nil 发送消息是完全可以的;没发生什么事。但是,如果您确实对其进行了初始化,并且仅将其设置为assign,那么它就不再是nil。一旦 ARC 释放它,_highscore 将指向一个不再存在的对象,这就是导致崩溃的原因。

【讨论】:

好的。是的,我之前曾尝试初始化数组,但后来我的应用程序就会崩溃。完全没有strong 引用导致了这个问题。非常感谢。我一直试图围绕这些概念展开思考。在我接受答案之前的一个后续问题:为什么没有强有力的参考会在这里有所作为? cmets 中没有足够的空间来查看我想说的内容,请参阅上面的编辑。 老兄!现在这完全有道理。感谢您的详尽解释...我不会再犯这个错误了!

以上是关于使用 NSData 和 NSMutableArrays 保存 OS X 游戏数据的主要内容,如果未能解决你的问题,请参考以下文章

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

SWIFT:将字节从 NSData 粘贴到 Struct?

NSData over GameKit 和 EXC_BAD_ACCESS 奇怪的问题

在 Objective-C 中获取 NSData 的 CRC 校验和

iOS 字典和NSData之间转换

如何使用 NSData 或使用数据字节目标 c 生成 pdf?