Where can I find a generic game state singleton for Objective C? - objective-c

I'm wanting to use, and store a game state singleton inside NSCoder, but I am finding it quite difficult to find a generic state management that saves and loads its data using the NSKeyedArchiver/NSCoder routines.
I'm wondering if someone can direct me to a good tutorial, or generic code for use as a game state singleton, with it saving/loading in NSCoder?
Thanks

I've been able to get a basic game state manager working from watching tutorials from 71squared.com
I use the SynthesizeSingleton.h from CocoaWithLove and am able to save and load states using NSCoder / archiving.
As it uses the singelton, I can reference it by writing:
GameStateManager *gsm = [GameStateManager sharedGameStateManager];
As below:
// GameStateManager.h file
#interface GameStateManager : NSObject
{
NSMutableArray *listOfPlayers;
}
#property (nonatomic, retain) NSMutableArray *listOfPlayers;
+ (GameStateManager *)sharedGameStateManager;
-(void)loadGameState;
-(void)saveGameState;
// GameStateManager.m
#import "GameStateManager.h"
#import "SynthesizeSingleton.h"
#import "Player.h"
#implementation GameStateManager
SYNTHESIZE_SINGLETON_FOR_CLASS(GameStateManager)
#synthesize listOfPlayers;
#pragma mark - Init
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
self.listOfPlayers = [NSMutableArray array];
}
return self;
}
#pragma mark - Memory management
-(void) dealloc
{
[super dealloc];
}
#pragma mark - Save/Load
-(void)saveGameState
{
NSLog(#"saveGameState");
// Set up the game state path to the data file that the game state will be save to
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *gameStatePath = [documentsDirectory stringByAppendingPathComponent:#"gameState.dat"];
// Set up the encoder and storage for the game state data
NSMutableData *gameData;
NSKeyedArchiver *encoder;
gameData = [NSMutableData data];
encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:gameData];
// Archive the object
[encoder encodeObject:self.listOfPlayers forKey:#"playerObjects"];
// Finish encoding and write to disk
[encoder finishEncoding];
[gameData writeToFile:gameStatePath atomically:YES];
[encoder release];
}
-(void)loadGameState
{
NSLog(#"loadGameState");
// Set up the game state path to the data file that the game state will be save to
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *gameStatePath = [documentsDirectory stringByAppendingPathComponent:#"gameState.dat"];
NSMutableData *gameData;
NSKeyedArchiver *decoder;
gameData = [NSData dataWithContentsOfFile:gameStatePath];
// Check to see if the .dat file exists, and load contents
if (gameData)
{
decoder = [[[NSKeyedUnarchiver alloc] initForReadingWithData:gameData] retain];
self.listOfPlayers = [[[decoder decodeObjectForKey:#"playerObjects"] retain] autorelease];
NSLog(#"Returned %d players", [self.listOfPlayers count]);
for (Player *p in self.listOfPlayers)
{
NSLog(#"p.name = %#", p.fname);
}
// Finished decoding, release
[decoder release];
[decoder autorelease];
} else {
}
}
In my Player.h/.m file I do this:
#interface Player : NSObject
<NSCoding>
{
NSString *fname;
}
#property(nonatomic,retain) NSString *fname;
// Player.m file
#pragma mark - Encoding
-(id)initWithCoder:(NSCoder *)aDecoder
{
//[self initWithObject:[aDecoder decodeObject];
self = [super init];
if (self) {
// Initialization code here.
self.fname = [aDecoder decodeObjectForKey:#"fname"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.fname forKey:#"fname"];
}
// ---
My follow-up questions are:
1) Am I meant to put a listOfPlayers in the GameStateManager?
--> Moved to a new question
2) In my encoder, I have to store each field individually, is it not possible to just store the actual object itself?
3) Does encoding of an object also encode all its children?
Lets say I have a Parent class (Player) and a Child class (Weapons). If I encode the Player object only, will it encode all the Weapons too automatically because they are linked?
Thanks.

Related

Can't retrieve data after storing to disk using NSKeyedArchiver

I'm storing a custom object in a dictionary which i'm then saving to disk using NSKeyedArchiver. In the same method where I save the data, I do a quick test at the end to make sure I can load the data but everything comes out null. Why is this happening? I've been following the instructions in this tutorial: http://cocoadevcentral.com/articles/000084.php
/** Interface of viewcontroller**/
#import "User.h"
#interface BWViewController : UIViewController
{
IBOutlet UITextField *userNameField;
IBOutlet UITextField *passwordField;
IBOutlet UILabel * loginStatus;
}
#property (nonatomic, copy) NSString *holdPassword, *holdUserName;
-(IBAction)signUpButton;
-(IBAction)loginButton;
-(NSString*) pathForDataFile;
-(void) saveDataToDisk:(User*) someUser;
-(User *) loadDataFromDisk:(NSString*) theKey;
#end
/**Implementation file **/
#import "BWViewController.h"
#interface BWViewController ()
#end
#implementation BWViewController
#synthesize holdPassword,holdUserName;
-(IBAction)signUpButton
{
User * firstUser = [[User alloc] init];
firstUser.userName = userNameField.text;
firstUser.password = passwordField.text;
/**To save to file**/
[self saveDataToDisk:firstUser];
loginStatus.text = #"Thanks for signing up";
}
-(NSString*) pathForDataFile
{
NSFileManager * fileManager = [NSFileManager defaultManager];
NSString *folder =#"~/Library/Application Support/signUpApp/";
folder = [folder stringByExpandingTildeInPath];
if([fileManager fileExistsAtPath:folder] == NO)
{
[fileManager createFileAtPath:folder contents:nil attributes:nil];
}
NSString *fileName = #"signUpApp.demo";
return [folder stringByAppendingPathComponent:fileName];
}
-(void) saveDataToDisk:(User*) someUser
{
User * savingUser = someUser;
NSString * path = [self pathForDataFile];
NSMutableDictionary * rootObject = [NSMutableDictionary dictionary];
[rootObject setValue:savingUser forKey:userNameField.text];
[NSKeyedArchiver archiveRootObject:rootObject toFile:path];
NSLog(#"saving user to disk %#", savingUser.userName);
/**Testing the load process**/
User * testLoadUser;
NSMutableDictionary *unRootObject;
unRootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
testLoadUser = [unRootObject valueForKey:userNameField.text];
NSLog(#"Testing the unarchive %# and %#", testLoadUser.userName, testLoadUser.password);
}
/**User implementation file**/
#import "User.h"
#implementation User
#synthesize userName, password;
- (void) encodeWithCoder: (NSCoder *)coder
{
[coder encodeObject: userName forKey:#"username"];
[coder encodeObject: password forKey:#"password"];
}
- (id) initWithCoder: (NSCoder *)coder
{
if (self = [super init])
{
self.userName = [coder decodeObjectForKey:#"username"];
self.password = [coder decodeObjectForKey:#"password"];
}
return self;
}
#end
This looks like iOS code, but you're using a Mac OS X convention for determining the folder for the path. In iOS, you should use the Documents folder.
For iOS, the path for the filename in the Documents folder should be:
- (NSString*) pathForDataFile
{
NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *path = [documentsPath stringByAppendingString:#"signUpApp.demo"];
return path;
}
I'd also suggest checking the return code from archiveRootObject method, to confirm whether the archive succeeded or not. No point in going further if that failed.
Also, in your old pathForDataFile, you're calling createFileAtPath:contents:attributes:, but I suspect you intended createDirectoryAtURL:withIntermediateDirectories:attributes:error:. The former is for creating a file, and the latter is for creating a folder. And your code is trying to create a folder.
Quick things you can check..
1) IS file is Actually created on the location ?
2) Is there any data (random chars) in that file ?
3) Is your code contains <NSCopying> protocol in your user.h class?
4) Make sure you don't have - (id) initWithCoder: (NSCoder *)coder and - (void) encodeWithCoder: (NSCoder *)coder in your header file.

How you save a class object being used by another class in Objective-C?

I'm making a Cookbook application for the iPad and iPod, and I have an array of my Recipe class in my Cookbook class.
#interface Cookbook : NSObject<NSCoding>{
NSMutableArray* recipes;
}
That's in my Cookbook class, and in my recipe class I have this:
#interface Recipe : NSObject<NSCoding>{
NSString* name;
NSMutableArray* ingredients; //List of ingredients for the recipe
UIImage* recipePicture;
NSMutableArray* instructions;
unsigned int prepTime;//in seconds
NSDate* dateAdded;
}
(I actually have more variables in here, but I didn't want to flood this with an excessive amount)
My problem is basically in the save/load feature. I've asked a similar question before here:
How can I save an Objective-C object that's not a property list object or is there a better way for this than a property list?
This made me decide it'd be best to use NSCoding, and I've already implemented a method for it, too, in accordance with the way that was suggested with NSCoding.
My primary problem is that I can't get the recipes to be stored and successfully retrieved.
I've also had trouble getting the directory to my RecipeList.plist file to store the recipes in.
Any help would be greatly appreciated as this has been the reason I can't continue making this application.
In my Cookbook.m I have:
-(id) initWithCoder:(NSCoder *)aDecoder{
NSLog(#"Init With Coder - Cookbook");
if(self = [super init]){
recipes = [aDecoder decodeObjectForKey:#"recipes"];
}
return self;
}
In my Recipe.m:
-(id) initWithCoder:(NSCoder *)aDecoder{
if(self = [super init]){
name = [aDecoder decodeObjectForKey:#"name"];
ingredients = [aDecoder decodeObjectForKey:#"ingreds"];
recipePicture = [aDecoder decodeObjectForKey:#"recipePict"];
}
return self;
}
Once again, I have more variables in there, this is just for simplicity.
Also, this is my attempt at getting a file path to RecipeList.plist:
+(NSString*) filePath{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
path = [path stringByAppendingPathComponent:#"RecipeList.plist"];
NSLog(#"%#",path);
return path;
}
My attempt at a save method in my AppDelegate.m:
-(void) save:(NSString *)path cookbook:(Cookbook *)cookbook{
BOOL b = [[NSFileManager defaultManager] fileExistsAtPath:path];
NSLog(#"File exists: %i",b); //1 = exists, 0 = doesn't
NSMutableData* data = [[NSMutableData alloc] init];
if(data){ //If the data object was successfully initialized
NSKeyedArchiver* archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
if(archiver){
//Encode the recipe using the coder method defined in recipe.
[archiver encodeInt:1 forKey:#"Version"];
[archiver encodeObject:cookbook forKey:#"Cookbook"];
[archiver finishEncoding];
[data writeToFile:path atomically:YES];
}
}
}
and my load method:
-(Cookbook *) loadCookbook:(NSString *)path{
BOOL b = [[NSFileManager defaultManager] fileExistsAtPath:path];
NSLog(#"File exists: %i",b);
Cookbook* ret = nil;
NSData* data = [NSData dataWithContentsOfFile:path];
if(data){
NSKeyedUnarchiver* unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
int version = [unarchiver decodeIntForKey:#"Version"];
if(version == 1){
ret = (Cookbook*) [unarchiver decodeObjectForKey:#"Cookbook"];
}
[unarchiver finishDecoding];
}
return ret;
}
I also have a save and load method for my Recipe class very similar to this.
Once again, any help would be greatly appreciated, and thank you for taking the time to read through this.
EDIT: Here's the encodeWithCoder method in Recipe.m with a few variables omitted for the sake of brevity:
-(void) encodeWithCoder:(NSCoder *)aCoder{
NSLog(#"Encoding Recipe.");
[aCoder encodeObject:name forKey:#"name"];
[aCoder encodeObject:ingredients forKey:#"ingreds"];
[aCoder encodeObject:recipePicture forKey:#"recipePict"];
[aCoder encodeObject:instructions forKey:#"instructs"];
[aCoder encodeObject:category forKey:#"categ"];
[aCoder encodeObject:dateAdded forKey:#"dateAdd"];
[aCoder encodeInt:prepTime forKey:#"prepTime"];
}
and in Cookbook.m:
-(void) encodeWithCoder:(NSCoder *)aCoder{
NSLog(#"Encoding cookbook.");
[aCoder encodeObject:recipes forKey:#"recipes"];
}
I've "almost" copy-pasted your code and after some minor adjustments I've got it up and running.
To the Cookbook, I've added:
- (id)init {
if (self = [super init]) {
recipes = [NSMutableArray array];
}
return self;
}
and
- (void)addRecipe:(Recipe *)recipe {
[recipes addObject:recipe];
}
You might have those already? I assume you're instantiating the array somewhere?
Then using this test-routine, the cookbook is created, used and saved perfectly fine:
NSString *path = [self filePath];
Cookbook *cookbook = [self loadCookbook:path];
if (cookbook) {
NSLog(#"Loaded cookbook: %#", cookbook);
}
else {
cookbook = [[Cookbook alloc] init];
}
Recipe *recipe = [[Recipe alloc] init];
recipe->name = #"The name";
[cookbook addRecipe:recipe];
[self save:path cookbook:cookbook];
NSLog(#"Saved cookbook: %#", cookbook);
fyi: I've made the Recipe's instance variables #public for brevity; hopefully you're using accessors (#property).
Do you use Automatic or Manual reference counting?

How to load an Objective C Singleton using NSCoding?

I am writing a basic game, I am using a GameStateManager which is a singleton and handles all state management, such as saves, loads, delete methods.
I have a separate singelton which handles all the Game stuff. The game object is inside the game state manager so I can save it out using NSCoding and Archiving.
When I save the state there appears to be no issues and the Game object (singleton) is saved properly.
However, when I try to load the state (by relaunching the app), the game object is always null.
Strangley, if I remove the singleton properties and make it a standard class, this issue goes away and I can load the class and all its properties without any major issues.
In summary, I do this:
GameStateManager = Singleton, handles all game state management (load, save) and has a game object (game)
Game = Singleton which handles things within the game and has NSCoding protocol employed.
Saving the game state with the game object is fine, the object is clearly there.
Loading the game state seems to make the game object null. It should be there, but for some reason it never loads it.
If I remove all the properties that make the game class a singelton and make it a normal class, the issue seems to go away.
I think it has something to do with the fact that Game is never init'd, but this does not make sense because I can get Game to load when it has no singleton properties.
Code now follows.
// GameStateManager.m
-(void)saveGameState
{
CCLOG(#"METHOD: saveGameState()");
self.lastSaveDate = [NSDate date];
NSMutableData *data;
NSString *archivePath = [NSTemporaryDirectory() stringByAppendingPathComponent:kGameSaveFile];
NSKeyedArchiver *archiver;
BOOL result;
data = [NSMutableData data];
archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:self.lastSaveDate forKey:#"lastSaveDate"];
[archiver encodeObject:self.game forKey:#"game"];
[archiver finishEncoding];
result = [data writeToFile:archivePath atomically:YES];
[archiver release];
}
-(void)loadGameState
{
CCLOG(#"METHOD: loadGameState()");
NSData *data;
NSKeyedUnarchiver *unarchiver;
NSString *archivePath = [NSTemporaryDirectory() stringByAppendingPathComponent:kGameSaveFile];
data = [NSData dataWithContentsOfFile:archivePath];
unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
// Customize unarchiver here
self.game = [unarchiver decodeObjectForKey:#"game"];
self.lastSaveDate = [unarchiver decodeObjectForKey:#"lastSaveDate"];
[unarchiver finishDecoding];
[unarchiver release];
CCLOG(#"Last Save Date = %#", self.lastSaveDate);
NSLog(#"game = %#", self.game);
}
// END OF GAMESTATEMANAGER
// -------------------------
// Game.h
#interface Game : NSObject
<NSCoding>
{
NSMutableArray *listOfCities;
NSMutableArray *listOfColors;
NSMutableArray *listOfPlayers;
}
#property (nonatomic, retain) NSMutableArray *listOfCities;
#property (nonatomic, retain) NSMutableArray *listOfColors;
#property (nonatomic, retain) NSMutableArray *listOfPlayers;
+(Game *) sharedGame;
//
// Game.m
// This is a cut-down version of the game object
// Game is a singelton
// The listOfCities, etc are arrays
//
#implementation Game
SYNTHESIZE_SINGLETON_FOR_CLASS(Game)
#synthesize listOfCities, listOfPlayers;
#pragma mark - NSCoding
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self)
{
self.listOfCities = [aDecoder decodeObjectForKey:#"listOfCities"];
self.listOfPlayers = [aDecoder decodeObjectForKey:#"listOfPlayers"];
NSLog(#"Cities = %d", [self.listOfCities count]);
NSLog(#"Players = %d", [self.listOfPlayers count]);
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
// Archive objects
NSLog(#"Cities = %d", [self.listOfCities count]);
NSLog(#"Players = %d", [self.listOfPlayers count]);
[aCoder encodeObject:self.listOfCities forKey:#"listOfCities"];
[aCoder encodeObject:self.listOfPlayers forKey:#"listOfPlayers"];
}
#end
My question is, how do I successfully save and load an Objective C singelton using NSCoding, and the Archiver?
Edit;
I have tried:
// In the GameStateManager
#pragma mark - NSCoding
-(id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
self.lastSaveDate = [[decoder decodeObjectForKey:#"lastSaveDate"] retain];
self.game = [[decoder decodeObjectForKey:#"game"] retain];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.lastSaveDate forKey:#"lastSaveDate"];
[encoder encodeObject:self.game forKey:#"game"];
}
and
// In Game.m
self.listOfCities = [[aDecoder decodeObjectForKey:#"listOfCities"] retain];
self.listOfPlayers = [[aDecoder decodeObjectForKey:#"listOfPlayers"] retain];
NSLog(#"Cities = %d", [self.listOfCities count]);
NSLog(#"Players = %d", [self.listOfPlayers count]);
Since you're dealing with a singleton, you only ever want a single instance of the class to exist at any time. So you will want to archive and unarchive that single instance only.
Consider this code (assumes ARC is enabled):
#interface Singleton : NSObject <NSCoding>
+ (id)sharedInstance;
#property (strong, nonatomic) NSString *someString;
#end
#implementation Singleton
+ (id)sharedInstance {
static Singleton instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[Singleton alloc] init];
});
return instance;
}
#pragma mark - NSCoding implementation for singleton
- (id)initWithCoder:(NSCoder *)aDecoder {
// Unarchive the singleton instance.
Singleton *instance = [Singleton sharedInstance];
[instance setSomeString:[aDecoder decodeObjectForKey:#"someStringKey"]];
return instance;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
// Archive the singleton instance.
Singleton *instance = [Singleton sharedInstance];
[aCoder encodeObject:[instance someString] forKey:#"someStringKey"]];
}
#end
Using this template, you can archive/unarchive the singleton, like this:
// Archiving calls encodeWithCoder: on the singleton instance.
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:[Singleton sharedInstance]] forKey:#"key"];
// Unarchiving calls initWithCoder: on the singleton instance.
[[NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:#"key"]];
try this in your initWithCoder method :
self.listOfCities = [[aDecoder decodeObjectForKey:#"listOfCities"] retain];
self.listOfPlayers = [[aDecoder decodeObjectForKey:#"listOfPlayers"] retain];
Easiest way:
1. Load singleton:
+ (AppState *)sharedInstance
{
static AppState *state = nil;
if ( !state )
{
// load NSData representation of your singleton here.
NSData *data =[[NSUserDefaults standardUserDefaults] objectForKey:#"appStateData"];
if ( data )
{
state = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
else
{
state = [[AppState alloc] init];
}
}
return state;
}
2. Save singleton on a disk
- (BOOL)save
{
NSData *appStateData = [NSKeyedArchiver archivedDataWithRootObject:self];
// now save NSData to disc or NSUserDefaults.
[[NSUserDefaults standardUserDefaults] setObject:appStateData forKey:#"appStateData"];
}
3. Implement NSCoding methonds:
- (id)initWithCoder:(NSCoder *)coder;
- (void)encodeWithCoder:(NSCoder *)coder;
Done!
I'm using a hybrid approach encompassing answers by Eric Baker and skywinder.
+ (GameStateManager *)sharedInstance {
static GameStateManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"key"];
if (data) {
instance = [NSKeyedUnarchiver unarchiveObjectWithData:data];
} else {
instance = [[GameStateManager alloc] init];
}
});
return instance;
}

Issue with save file to Documents NSMutableArray with NSObjects

I'm a newbie iOS developer.
I wrote a small application that save an NSMutableArray array with my objects that derived from NSObject.
Application do the save but the file isn't created in document directory and application can't read.
this issue is both on the simulator and my iPhone 3gs 4.2.1
My NSMutableArray definition inside the appDelegate class:
#property (nonatomic,retain, readwrite) NSMutableArray *places;
My NSObject class:
#import <Foundation/Foundation.h>
#interface Place : NSObject {
NSString *name;
NSString *location;
}
-(id) init:(NSString *)name: (NSString *)location;
#property (retain,nonatomic,readwrite) NSString *name;
#property (retain,nonatomic,readwrite) NSString *location;
#end
My StorageService library class:
#import "StorageService.h"
#implementation StorageService
-(id) init {
self = [super init];
if (self != nil) {
}
return self;
}
-(void) saveArrayToFile:(NSString*) filename : (NSMutableArray *)arrayToSave{
// get full path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *fullPath = [paths objectAtIndex:0];
fullPath = [fullPath stringByAppendingPathComponent:filename];
NSLog(#"Save in %#",fullPath);
[arrayToSave writeToFile:fullPath atomically:YES];
}
-(NSMutableArray*) readArrayFromFile:(NSString *)filename {
// get full path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *fullPath = [paths objectAtIndex:0];
fullPath = [fullPath stringByAppendingPathComponent:filename];
if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath]) {
NSMutableArray *data = [[NSMutableArray alloc] initWithContentsOfFile:fullPath];
if (data == nil) {
data = [[NSMutableArray alloc] init];
}
NSLog(#"Read from %#",fullPath);
return data;
} else {
NSMutableArray *data = [[NSMutableArray alloc] initWithContentsOfFile:fullPath];
return data;
}
}
-(void) dealloc {
[super dealloc];
}
#end
and My functions in the appDelegate:
-(void) saveApplicationData {
[self.storageService saveArrayToFile : PLACES_FILE : self.places];
}
-(void) loadApplicationData {
self.places = [self.storageService readArrayFromFile:PLACES_FILE];
}
Here is my class that holds constant to filename:
#import <Foundation/Foundation.h>
extern NSString * const PLACES_FILE = #"Places.dat";
#interface ApplicationConstants : NSObject {
}
#end
So what is wrong?
Thank you guys.
What you want is to let Place conform to the NSCoding protocol, to allow for serialization to and from files (and in memory data if wanted)
Extend Place as (I have also changed the name of the init method as your name was against every naming practice iOS has):
#interface Place : NSObject <NSCoding> {
NSString *name;
NSString *location;
}
-(id)initWithName:(NSString *)name location:(NSString *)location;
#property (retain,nonatomic,readwrite) NSString *name;
#property (retain,nonatomic,readwrite) NSString *location;
#end
Your implementation is quite simple but you also need to implement two methods defined by the NSCoding protocol:
#implementation Place
#synthesize name, location;
-(id)initWithName:(NSString *)aName location:(NSString *)aLocation {
self = [super init];
if (self) {
self.name = aName;
self.location = aLocation;
}
return self;
}
-(id)initWithWithCoder:(NSCoder)decoder {
self = [super initWithCoder:decoder];
if (self) {
self.name = [decoder decodeObjectForKey:#"name"];
self.location = [decoder decodeObjectForKey:#"location";
}
return self;
}
-(void)encodeWithCoder:(NSCoder*)encoder {
[encoder encodeObject:self.name forKey:#"name"];
[encoder encodeObject:self.location forKey:#"location"];
[super encodeWithCoder:encoder];
}
#end
With this in place, saving the places array to disk is as easy as:
[NSKeyedArchiver archiveRootObject:places toFile:path];
And decoding just as easy:
places = [[KSKeyUnarchiver unarchiveObjectWithFile:path] retain];
To use writeToFile objects in array need to be plist capable type (NSDate, NSDate, NSString, NSArray, NSDictionary)
Implement NSCoding on the objects in array and use NSKeyedArchiver to serialize/deserialize.
write:
[NSKeyedArchiver archiveRootObject:myArray toFile:self.places];
read:
[NSKeyedUnarchiver unarchiveObjectWithFile:path];
More info can be found here:
Persisting Custom Objects

How do you save data with NSKeyedArchiver?

Hi, I am trying to save an object from a class I have created. It is called shot and Contains 5 variables I wish to save. Here is the .h file--- It cut off NSCoding and NSMutableCopying Protocals and my imports, but they are there.
#import <Foundation/Foundation.h>
#interface Shot : UIButton <NSCoding, NSMutableCopying> {
int x;
int y;
int location;
int quarter;
bool made;
int miss;
int make;
}
#property()int x;
#property()int y;
#property()int location;
#property()int quarter;
#property()bool made;
-(void)set_x:(int)my_x set_y:(int)my_y set_quarter:(int)my_quarter set_made:(bool)my_made set_location:(int)my_location;
-(void)set_miss_AND_set_make;
#end
**Here are the methods I made to save the data in my .m file---**
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeInt:x forKey:#"x"];
[aCoder encodeInt:y forKey:#"y"];
[aCoder encodeInt:location forKey:#"location"];
[aCoder encodeInt:quarter forKey:#"quarter"];
[aCoder encodeBool:made forKey:#"made"];
}
-(id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
x = [aDecoder decodeIntForKey:#"x"];
y = [aDecoder decodeIntForKey:#"y"];
location = [aDecoder decodeIntForKey:#"location"];
quarter = [aDecoder decodeIntForKey:#"quarter"];
made = [aDecoder decodeBoolForKey:#"made"];
}
return self;
}
-(id)mutableCopyWithZone:(NSZone *)zone {
Shot *newShot = [[Shot allocWithZone:zone]init];
[newShot set_x:x set_y:y set_quarter:quarter set_made:made set_location:location];
return newShot;
}
I can't seem to get my data to saved when I use these methods
-(NSString *)getPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentFolder = [paths objectAtIndex:0];
NSFileManager *fm = [[NSFileManager alloc]init];
if ([fm fileExistsAtPath:documentFolder] == NO) {
[fm createDirectoryAtPath:documentFolder withIntermediateDirectories:YES attributes:nil error:nil];
}
return [documentFolder stringByAppendingFormat:#"iStatTrackInfo.archive"];
}
-(void)saveData {
NSString *path = [self getPath];
[NSKeyedArchiver archiveRootObject:shotArray toFile:path];
}
-(void)loadData {
NSString *path = [self getPath];
NSFileManager *fm = [[NSFileManager alloc]init];
if ([fm fileExistsAtPath:path] == YES) {
shotArray = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
return;
}
NSEnumerator *enumOne = [shotArray objectEnumerator];
Shot *shotObject = [[Shot alloc]init];
while (shotObject = [enumOne nextObject]) {
Shot *shotShot = [[Shot alloc]init];
shotShot = [shotObject mutableCopy];
shotShot.frame = CGRectMake(0, 0, 50, 50);
shotShot.backgroundColor = [UIColor whiteColor];
[self addSubview:shotShot];
}
}
I have set the save and load methods up to buttons, but my data still won't save. Please help!!!
I have finally solved the problem. Other than the above help, .archive isn't a file type. You can't save a .archive, you can only save a .arch or a .plist That was my main problem.
First, you are leaking memory like a sieve. Lots of alloc/init & mutableCopy, no releases. I'd suggest reading the memory management guide.
Next, your method names are not following the Cocoa conventions. These:
-(void)set_x:(int)my_x set_y:(int)my_y set_quarter:(int)my_quarter set_made:(bool)my_made set_location:(int)my_location;
-(void)set_miss_AND_set_make;
Should be something like:
-(void) resetMissAndMake; // or just missAndMake or activateMissAndMake
-(void) setX:(NSInteger)xValue y:(NSInteger)yValue quarter:(NSInteger)quarterValue location:(NSInteger)aLocation;
Also, getPath should just be path. Methods prefixed with get are both very rare and have a very specific meaning.
This is also nonsense:
Shot *shotObject = [[Shot alloc]init];
while (shotObject = [enumOne nextObject]) {
Shot *shotShot = [[Shot alloc]init];
shotShot = [shotObject mutableCopy];
There is no need for either of the [[Shot alloc]init] calls (those are both leaks).
Finally, your encoding methods are implemented incorrectly. As the documentation states, you need to call super as appropriate.
Specifically, your encodeWithCoder: must invoke super's implementation of same and initWithCoder: should call super's initWithCoder:, not init.