使用 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.
这通常意味着您使用包含 alloc
或 copy
的方法创建的任何对象,但它也属于便利方法,例如 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 之前,这意味着在其上调用 release
或 autorelease
。
在 ARC 世界中,内存管理主要 为您处理,但您仍然需要稍微了解其背后的概念才能理解。基本上,如果您创建一个对象(使用包含 alloc
或 copy
的方法,或 array
等便捷方法),那么您就拥有该对象。您通过创建对该对象的 strong
引用来表达对该对象的所有权。如果你没有这样做(比如,你的引用是 weak
或 assign
),那么 ARC 将(正确地)假设你已经完成了那个对象,并会在它决定你时立即释放它完成它。在上面的代码中,可以肯定的是,一旦 initWithCoder
返回,ARC 选择释放 _hiscore
。稍后,您的代码尝试访问 _hiscore
,但它已被释放,因此您崩溃了。
没有代码初始化 _highscore
,它没有崩溃。因为 _highscore
始终是 nil
,并且在 Objective-C 中向 nil
发送消息完全没问题;什么都没发生。但是如果你真的初始化它,并且只将它设置为 assign
,那么它就不再是 nil
。一旦 ARC 释放它,_highscore
指向一个不再存在的对象,这就是导致崩溃的原因。
我正在尝试将几个级别的高分存储在 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.
这通常意味着您使用包含 alloc
或 copy
的方法创建的任何对象,但它也属于便利方法,例如 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 之前,这意味着在其上调用 release
或 autorelease
。
在 ARC 世界中,内存管理主要 为您处理,但您仍然需要稍微了解其背后的概念才能理解。基本上,如果您创建一个对象(使用包含 alloc
或 copy
的方法,或 array
等便捷方法),那么您就拥有该对象。您通过创建对该对象的 strong
引用来表达对该对象的所有权。如果你没有这样做(比如,你的引用是 weak
或 assign
),那么 ARC 将(正确地)假设你已经完成了那个对象,并会在它决定你时立即释放它完成它。在上面的代码中,可以肯定的是,一旦 initWithCoder
返回,ARC 选择释放 _hiscore
。稍后,您的代码尝试访问 _hiscore
,但它已被释放,因此您崩溃了。
没有代码初始化 _highscore
,它没有崩溃。因为 _highscore
始终是 nil
,并且在 Objective-C 中向 nil
发送消息完全没问题;什么都没发生。但是如果你真的初始化它,并且只将它设置为 assign
,那么它就不再是 nil
。一旦 ARC 释放它,_highscore
指向一个不再存在的对象,这就是导致崩溃的原因。