patternMinor
Save Games on iOS with NSCoding
Viewed 0 times
ioswithnscodinggamessave
Problem
This is the second time I have implemented saving and loading for a game using Objective-C. I am using the built in NSCoding methods. I would love to hear opinions about NSCoding and whether or not it is a viable option for saving and loading games. My code does function, however there is a lot of boilerplate code required to make it work. Maybe there is a way to reduce the lines of code required that I don't know about.
I did find a library that supposedly automated part of this process, however that library only encodes the @properties of the class, and I am also encoding private instance variables as well. I also was not planning to use an external library, although I will take any recommendations given.
First, here are the saving and loading methods in the SKScene:
```
#pragma mark - Save and Load
-(void) saveGameToSlot:(NSString *)saveSlot {
NSLog(@"Saving game %@", saveSlot);
[self saveCustomObject:_game key:saveSlot];
[self closeSaveMenu];
}
-(void) loadGameInSlot:(NSString *)loadSlot {
NSString *savePath = [[self applicationDocumentsPath] stringByAppendingPathComponent:loadSlot];
BOOL saveExists = [[NSFileManager defaultManager]fileExistsAtPath:savePath];
if (saveExists) {
[self prepareForLoading];
_game = [self loadCustomObjectWithKey:loadSlot];
[self createUIAndRenderer];
[self createSceneElements];
NSLog(@"Loading game %@", loadSlot);
[self closeSaveMenu];
}
}
-(void) saveCustomObject:(DTGame )object key:(NSString )key {
NSString *path = [[self applicationDocumentsPath] stringByAppendingPathComponent:key];
[NSKeyedArchiver archiveRootObject:object toFile:path];
}
-(DTGame ) loadCustomObjectWithKey:(NSString )key {
NSString *path = [[self applicationDocumentsPath] stringByAppendingPathComponent:key];
DTGame *object = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
return object;
}
-(void) prepareForLoading {
[_world removeAllChildren];
I did find a library that supposedly automated part of this process, however that library only encodes the @properties of the class, and I am also encoding private instance variables as well. I also was not planning to use an external library, although I will take any recommendations given.
First, here are the saving and loading methods in the SKScene:
```
#pragma mark - Save and Load
-(void) saveGameToSlot:(NSString *)saveSlot {
NSLog(@"Saving game %@", saveSlot);
[self saveCustomObject:_game key:saveSlot];
[self closeSaveMenu];
}
-(void) loadGameInSlot:(NSString *)loadSlot {
NSString *savePath = [[self applicationDocumentsPath] stringByAppendingPathComponent:loadSlot];
BOOL saveExists = [[NSFileManager defaultManager]fileExistsAtPath:savePath];
if (saveExists) {
[self prepareForLoading];
_game = [self loadCustomObjectWithKey:loadSlot];
[self createUIAndRenderer];
[self createSceneElements];
NSLog(@"Loading game %@", loadSlot);
[self closeSaveMenu];
}
}
-(void) saveCustomObject:(DTGame )object key:(NSString )key {
NSString *path = [[self applicationDocumentsPath] stringByAppendingPathComponent:key];
[NSKeyedArchiver archiveRootObject:object toFile:path];
}
-(DTGame ) loadCustomObjectWithKey:(NSString )key {
NSString *path = [[self applicationDocumentsPath] stringByAppendingPathComponent:key];
DTGame *object = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
return object;
}
-(void) prepareForLoading {
[_world removeAllChildren];
Solution
Is this method of implementing saving and loading reliable and efficient?
It should be reliable. And if saving all of this information is necessary, then this is almost certainly the most efficient way to do it all.
The way to improve efficiency further is to ask yourself what datapoints are absolutely necessary to write and read later, and what datapoints can be derived from the necessary ones without explicitly being saved? If it can be implicitly determined, you don't necessarily need to save it.
I hate that I have to use strings to access the properties, because there is no auto correction for the text
While you do have to use strings for the keys, you can (and should) get auto-complete to help you by defining the keys as named constants. In all of my iOS projects, I always have multiple files where my global constants such as user defaults keys are defined. In this case, these strings could be declared simple within the current file as they're only used in two methods, but nonetheless, it would still be good to do it even if they're only used in two spots.
One other thing to think about is actually using
This won't make loading a game any faster or efficient, as at the end of the day, the loading process would be the same as what you're already doing, however, using
When you save to
You can call
What I'd recommend is only saving the currently active game to
It should be reliable. And if saving all of this information is necessary, then this is almost certainly the most efficient way to do it all.
The way to improve efficiency further is to ask yourself what datapoints are absolutely necessary to write and read later, and what datapoints can be derived from the necessary ones without explicitly being saved? If it can be implicitly determined, you don't necessarily need to save it.
I hate that I have to use strings to access the properties, because there is no auto correction for the text
While you do have to use strings for the keys, you can (and should) get auto-complete to help you by defining the keys as named constants. In all of my iOS projects, I always have multiple files where my global constants such as user defaults keys are defined. In this case, these strings could be declared simple within the current file as they're only used in two methods, but nonetheless, it would still be good to do it even if they're only used in two spots.
One other thing to think about is actually using
NSUserDefaults. You've already got init/encodeWithConder methods written, so now you can store these to NSUserDefaults.This won't make loading a game any faster or efficient, as at the end of the day, the loading process would be the same as what you're already doing, however, using
NSUserDefaults for at least the current game being played should allow you to take regular snapshots of the current game state and auto-save without the player even noticing.When you save to
NSUserDefaults using setValue:forKey:, at first, you're just setting the value in temporary memory. This is no different from setting a value in a NSMutableDictionary. It's quite quick. You just pointing to a memory address.You can call
synchronize on NSUserDefaults to force it to write everything in the temporary memory to permanent storage, and sometimes this can be appropriate, but you don't want to do this frequently. Instead, NSUserDefaults works in the background and wait till the processor has spare time and then writes the values to permanent storage. I'm pretty sure the only way the data in NSUserDefaults can be lost is if the phone suddenly died unexpectedly. Even if the app is killed or the phone is powered off normally, the data in NSUserDefaults will be saved before it's lost.What I'd recommend is only saving the currently active game to
NSUserDefaults however. Just before a different game becomes active, the data from NSUserDefaults should be saved in the manner you're already using here.Context
StackExchange Code Review Q#55527, answer score: 6
Revisions (0)
No revisions yet.