使用 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)是:
您拥有通过为它分配内存或复制它创建的任何对象。
这通常意味着您使用包含alloc
或copy
的方法创建的任何对象,但它也适用于array
等便捷方法。注意[NSMutableArray array]
完全等同于[[NSMutableArray alloc] init]
;使用前者的唯一原因是它减少了打字(包括我自己在内的许多人都喜欢)。
那么,
如果您拥有一个对象,无论是通过创建它还是表达所有权权益,您都有责任在不再需要它时释放它。
在 ARC 之前,这意味着调用 release
或 autorelease
。
在 ARC 世界中,内存管理大部分由您负责,但您仍然需要了解其背后的概念才能使其有意义。基本上,如果您创建一个对象(使用包含alloc
或copy
的方法,或array
等便利方法),那么您就拥有该对象。您可以通过创建对该对象的strong
引用来表达该对象的所有权。如果您没有这样做(例如,您的参考是weak
或assign
),那么 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 游戏数据
NSData over GameKit 和 EXC_BAD_ACCESS 奇怪的问题