Archive/Unarchive Parent-Child objects with a key - objective-c

I have a class A and a subclass B:
#interface A:NSObject<NSCoding>
String a;
String b;
#end
#interface B: A<NSCoding>
int c;
// `a` and `b` attributes are inherited
#end;
I want to archive some objects of A and some of B using attribute a as key.
The encoding and decoding protocols in both class have been defined as:
#implementation A
...
- (void) encodeWithCoder:(NSCoder *)encoder {
//[super encodeWithCoder:encoder];
[encoder encodeObject:self forKey:a];
}
- (id)initWithCoder:(NSCoder *)decoder {
// self = [super initWithCoder:decoder];
if (self=[super init]) {
self = [decoder decodeObjectForKey:a];
}
return self;
}
#end
#implementation B
..
- (void) encodeWithCoder:(NSCoder *)encoder {
[super encodeWithCoder:encoder];
[encoder encodeObject:self forKey:super.a];
}
- (id)initWithCoder:(NSCoder *)decoder {
//self = [super initWithCoder:decoder];
if (self) {
self = [decoder decodeObjectForKey:super.a];
}
return self;
}
For some object list dataArray = [A("One"), B("Two")], the archiving logic is
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dataArray];
NSError* error = nil;
BOOL success = [data writeToFile: #"xyz.dat" options:0 error: &error];
if (!success)
NSLog( #"error = %#", [error description] );
Again reading from the archived data:
NSData *lD = [[NSData alloc] initWithContentsOfFile:#"xyz.dat"];
NSKeyedUnarchiver *arc = [[NSKeyedUnarchiver alloc] initForReadingWithData:lD];
Now for the object with the attribute a as"One"
While trying to read the object back from archived data, i am getting null!
NSLog(#"Value - %#",[arc decodeObjectForKey:#"One"]);

Maybe,the problem was with encoding self.
I did the following changes:
#implementation A
...
- (void) encodeWithCoder:(NSCoder *)encoder {
if(self.a!=nil)
[encoder encodeObject:self.a forKey:#"a"];
if(self.b!=nil)
[encoder encodeObject:self.b forKey:#"b"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self=[super init];
if (self!=nil) {
self.a= [decoder decodeObjectForKey:#"a"];
self.b= [decoder decodeObjectForKey:#"b"];
}
return self;
}
#end
#implementation B
..
- (void) encodeWithCoder:(NSCoder *)encoder {
[super encodeWithCoder:encoder ];
[encoder encodeObject:self.c forKey:#"c"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if ( (self = [super initWithCoder:decoder ]) )
{
self.c= [decoder decodeObjectForKey:#"c"];
}
return self;
}
While encoding data:
NSMutableDictionary *dict = [NSMutableDictionary new];
[dict setObject:[[A alloc]init()] forKey:#"One"];//say
[dict setObject:[[B alloc]init()] forKey:#"Two"];//say
NSMutableData *yourData = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:yourData];
for (NSString* key in dict) {
id value = [dict objectForKey:key];
[archiver encodeObject:value forKey: key];
}
[ archiver finishEncoding];
[yourData writeToFile:#"xyz.dat" atomically:YES];
[yourData release];
[archiver release];
And decoding:
NSData *data = [[NSData alloc] initWithContentsOfFile:#"xyz.dat"];
NSKeyedUnarchiver *keyArc= [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSLog(#"%#",[keyArc decodeObjectForKey:#"One"]);
NSLog(#"%#",[keyArc decodeObjectForKey:#"Two"]);
[keyArc finishDecoding];
Ref: https://stackoverflow.com/a/10929284/2301570

Related

need to save contents of table view as txt file and the reload it later

i need to be able to save the contents of my table view which is given data by an NSMutableArray to a txt file and then need to reopen that file automatically when the window containing the table view. i am making an application for mac
thanks
this is the code for the data source:
#import "tableViewData.h"
#import "Customer.h"
#implementation tableViewData
-(id) init
{
self = [super init];
if (self) {
list = nil;
filepath = #"/Users/Gautambir/Desktop/CustomerNames.txt";
if ([[NSFileManager defaultManager]fileExistsAtPath:filepath]) {
list = [[NSMutableArray alloc] initWithContentsOfFile:filepath];
}
else
list = [[NSMutableArray alloc]initWithObjects:name,memberNumber ,nil];
[list writeToFile:filepath atomically:YES];
}
return self;
}
-(NSInteger)numberOfRowsInTableView:(NSTableView *)tableView{
return [list count];
}
-(id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row{
Customer *Customer = [list objectAtIndex:row];
NSString *identifier = [tableColumn identifier];
return [Customer valueForKey:identifier];
}
-(void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row{
Customer *Customer = [list objectAtIndex:row];
NSString *identifier = [tableColumn identifier];
[Customer setValue:object forKey:identifier];
}
-(void)save{
[list writeToFile:filepath atomically:YES];
}
-(IBAction)add:(id)sender{
[list addObject:[[Customer alloc]init]];
[tableView reloadData];
NSLog (#"array:%#",list);
}
-(IBAction)remove:(id)sender{
NSInteger row = [tableView selectedRow];
if (row != -1) {
[list removeObjectAtIndex:row];
}
[tableView reloadData];
}
-(void)dealloc
{
[super dealloc];
}
#end
and this is the .m file for the customer object class:
#import "Customer.h"
#implementation Customer
#synthesize name;
#synthesize memberNumber;
-(id) init
{
self = [super init];
if(self) {
name = #"Test";
int i = arc4random()%1000000000000000000;
if (i<0) {
memberNumber = i*-1;
}
else
memberNumber = i;
}
return self;
}
-(void)dealloc
{
[name release];
[super dealloc];
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self)
{
name = [[aDecoder decodeObjectForKey:#"name"]retain];
memberNumber = [aDecoder decodeIntForKey:#"memberNumeber"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:name forKey:#"name"];
[aCoder encodeInt:memberNumber forKey:#"memberNumber"];
}
#end
strong text
im sorry to post another answer - i misread the tags, and my first answer was relying to iOS.
this is the way to do it in OSX:
saving
NSMutableArray *array = ... //your array
[array addObject:...];
[array addObject:...];
[array addObject:...];
...
// then use an NSData to store the array
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
NSString *path = #"/Users/User/path...";
[data writeToFile:path options:NSDataWritingAtomic error:nil];
retrieving
NSMutableArray *archive = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
sebastian

NSKeyedArchiver archivedDataWithRootObject: does not call encodeWithCoder

I cant get the archivedDataWithRootObject method to call encodeWithCoder, it seems pretty simple from what I understand but it doesn't work.
Here's the code:
#import <Foundation/Foundation.h>
#interface Details : NSObject <NSCoding>{
NSMutableArray *myArray;
}
#property(nonatomic,retain) NSMutableArray *myArray;
#end
#import "Details.h"
#implementation Details
#synthesize myArray;
-(id)init{
[super init];
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
NSLog(#"initWithCoder");
if (self = [super init])
{
self.myArray = [[aDecoder decodeObjectForKey:#"details"]retain];
}
return self;
}
- (NSMutableArray*)getDetails{
NSLog(#"getDetials");
// NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *details = [[NSUserDefaults standardUserDefaults] objectForKey:#"details"];
if (details != nil){
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:details];
if (oldSavedArray != nil)
self.myArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
else
self.myArray = [[NSMutableArray alloc] initWithCapacity:1];
}
return self.myArray;
}
- (void) addDetails:(NSMutableArray *)details{
NSLog(#"add Details");
[self.myArray addObject:details];
[NSKeyedArchiver archivedDataWithRootObject:self.myArray];
}
- (void) encodeWithCoder:(NSCoder *)coder;
{
NSLog(#"encodeWithCoder");
[coder encodeObject:self.myArray forKey:#"details"];
}
#end
NSLog(#"encodeWithCoder") doesn't run.
I might be missing something, If anyone has any ideas it would be appreciated.
[NSKeyedArchiver archivedDataWithRootObject:self.myArray];
You are telling the NSKeyedArchiver to archive the myArray variable, not the object you have just implemented <NSCoding> with!
Also that method returns NSData * which you then have to write to disk separately if needed.
Try this:
NSData * archivedData = [NSKeyedArchiver archivedDataWithRootObject:self];

Init a NSMutableArray in a singelton that saves as a state

Im am using a pattern from the book Learning Cocos2D. I have a GameState class that is a singleton that can be saved as a state. The BOOL soundOn gets init as false the first time the class gest created, but the array levelProgress dosent. I made comments on all the lines on which I have tried to init the array.
The class uses a helper that loads and saves the data.
When i try 1 or 2 i get "instance variable 'levelProgress' accessed in class method" error.
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface GameState : NSObject <NSCoding> {
BOOL soundOn;
NSMutableArray *levelProgress;
}
+ (GameState *) sharedInstance;
- (void)save;
#property (assign) BOOL soundOn;
#property (nonatomic, retain) NSMutableArray *levelProgress;
#end
#import "GameState.h"
#import "GCDatabase.h"
#implementation GameState
#synthesize soundOn;
#synthesize levelProgress;
static GameState *sharedInstance = nil;
+(GameState*)sharedInstance {
#synchronized([GameState class])
{
if(!sharedInstance) {
sharedInstance = [loadData(#"GameState") retain];
if (!sharedInstance) {
[[self alloc] init];
// 1. levelProgress = [[NSMutableArray alloc] init];
}
}
return sharedInstance;
}
return nil;
}
+(id)alloc
{
#synchronized ([GameState class])
{
NSAssert(sharedInstance == nil, #"Attempted to allocate a \
second instance of the GameState singleton");
sharedInstance = [super alloc];
// 2. levelProgress = [[NSMutableArray alloc] init];
return sharedInstance;
}
return nil;
}
- (void)save {
saveData(self, #"GameState");
}
- (void)encodeWithCoder:(NSCoder *)encoder {
// 3. levelProgress = [[NSMutableArray alloc] init];
[encoder encodeBool:currentChoosenCountry forKey:#"soundOn"];
[encoder encodeObject:levelProgress forKey:#"levelProgress"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if ((self = [super init])) {
// 4. levelProgress = [[NSMutableArray alloc] init];
soundOn = [decoder decodeBoolForKey:#"soundOn"];
levelProgress = [decoder decodeObjectForKey:#"levelProgress"];
}
return self;
}
#end
Solution:
*I just added av init method...*
-(id)init {
self = [super init];
if (self != nil) {
levelProgress = [[NSMutableArray alloc] init];
}
return self;
}
Try this (this code may contain syntax or logic errors - I wrote it in notepad):
#interface GameState : NSObject <NSCoding>
#property (nonatomic, readwrite) BOOL soundOn;
#property (nonatomic, retain) NSMutableArray *levelProgress;
+ (GameState *)sharedState;
- (void)writeDataToCache;
#end
//
#implementation GameState
#synthesize soundOn, levelProgress;
#pragma mark - Singleton
static GameState *sharedState = nil;
+ (void)initialize {
static BOOL initialized = NO;
if (!initialized) {
initialized = YES;
if ([[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:#"%#/gameState", pathCache]]) {
NSData *encodedObject = [[NSData alloc] initWithContentsOfFile:[NSString stringWithFormat:#"%#/gameState", pathCache]];
data = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
}
else
data = [[GameState alloc] init];
}
}
+ (GameState *)sharedState {
return sharedState;
}
#pragma mark - Initialization
- (id)init {
if (self = [super init]) { // will be inited while application first run
soundOn = NO;
levelProgress = [[NSMutableArray alloc] init];
return self;
}
return nil;
}
#pragma mark - Coding Implementation
- (void)writeDataToCache { // use this method to save current state to cache
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:self];
if([[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:#"%#/GameState", pathCache]])
[[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:#"%#/GameState", pathCache] error:nil];
[[NSFileManager defaultManager] createFileAtPath:[NSString stringWithFormat:#"%#/GameState", pathCache] contents:encodedObject attributes:nil];
NSLog(#"GameState was saved successfully.");
}
- (void)encodeWithCoder:(NSCoder*)encoder {
[encoder encodeBool:self.soundOn forKey:#"soundOn"];
[encoder encodeObject:self.levelProgress forKey:#"levelProgress"];
}
- (id)initWithCoder:(NSCoder*)decoder {
if ((self = [super init])) {
self.soundOn = [decoder decodeBoolForKey:#"soundOn"];
self.levelProgress = [decoder decodeObjectForKey:#"levelProgress"];
NSLog(#"GameState was inited successfully");
return self;
}
return nil;
}
#end
1 & 2 are the same (and should not work, since levelProgress is an instance variable), 3 and 4 should be encoding/decoding the array, not initializing it anew.
1 and 2 are a mistake - you can't access instance variable without specifying an instance.
You can fix 1 by changing the line to sharedInstance.levelProgress = ....
2 is just a bad idea, you're breaking common convention of initializing using init... methods, it may surprise and cause problems for an other programmer who'll be working on the same code later.
3 and 4 are ok but if loadData fails they will not be executed and the object will be initialized with ordinary init and the array will be nil pointer:
[[self alloc] init];
you should also override init and initialize the properties there.

How to store CCSprite in NSUserDefault

I have a strange problem while working with CCSprite subclass Creature.
Lets,my object is Creature* creature;
The class Creature declaration-
#interface Creature : CCSprite <NSCoding>{
int creatureAge;
NSString *creatureName;
}
Implementation
+(id)initializeCreatureWithType:(int)type
{
Creature *creature = nil;
creature = [[[super alloc] initWithFile:[NSString stringWithFormat:#"ch%i_default.png",type]]autorelease];
return creature;
}
The problem is when i store my Creature class object 'creature' in NSUserDefault using-
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.creatureName forKey:#"creatureName"];
[encoder encodeInt:self.creatureAge forKey:#"creatureAge"];
}
And the decode it with-
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
self.creatureName = [decoder decodeObjectForKey:#"creatureName"];
self.creatureAge= [decoder decodeIntForKey:#"creatureAge"];
}
Then save the creature object using-
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:creature];
[defaults setObject:myEncodedObject forKey:#"my_pet"];
And the load-
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [defaults objectForKey:#"my_pet"];
Creature* newcreature = (Creature *)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
The problem is that when I load this i get the property value of previously stored creature, but the image that is assigned to previous creature perhaps does not copied. Because if i add the newcreature to any CCLayer it does not display any image, though it get the property value of previous creature.
What can I do now to get the newcreature with image? is it needed to add image name as a separate property???
You could simply store the type as well and then do it like this:
#interface Creature : CCSprite {
int creatureType;
int creatureAge;
NSString *creatureName;
}
+ (id)creatureWithType:(int)type;
- (id)initWithCreatureType:(int)type;
#end
#implementation Creature
+ (id)creatureWithType:(int)type
{
return [[[[self class] alloc] initWithCreatureType:type] autorelease];
}
- (id)initWithCreatureType:(int)type
{
self = [super initWithFile:[NSString stringWithFormat:#"ch%i_default.png", type]];
if (!self) return nil;
creatureType = type;
return self;
}
- (id)initWithCoder:(NSCoder *)decoder {
int type = [decoder decodeIntForKey:#"creatureType"];
self = [self initWithCreatureType:type];
if (!self) return nil;
self.creatureName = [decoder decodeObjectForKey:#"creatureName"];
self.creatureAge= [decoder decodeIntForKey:#"creatureAge"];
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeInt:creatureType forKey:#"creatureType"];
[encoder encodeObject:self.creatureName forKey:#"creatureName"];
[encoder encodeInt:self.creatureAge forKey:#"creatureAge"];
}
#end
You might want to expose creatureType via a property as well. Note that instead of initializeCreatureWithType: it's "more Cocoa" to use the name creatureWithType:.

Storing NSMutableArray of UIViews in NSUserDefaults

I have read several posts about this but I'm not able to fix the error. If someone could please help.
Here is the code I use. I have an NSMutableArray called list.
-(void) awakeFromNib
{
prefs=[[NSUserDefaults standardUserDefaults]retain];
if ([prefs arrayForKey:#"list"]) {
list=[[NSMutableArray alloc]initWithArray:[prefs objectForKey:#"list"]];
}
else {
list=[[NSMutableArray alloc]init];
}
}
-(void)saveData
{
NSLog(#"saving data!");
[prefs setObject:list forKey:#"list"];
}
- (void)dealloc {
[self saveData];
[prefs synchronize];
[prefs release];
}
You cannot store UIView instances in the user defaults, but only objects that can be serialized in a property list (see here) Also, as #Justin said, do not retain or release the defaults object.
Thank you. However, I had earlier read that you cannot save an NSMutableArray in NSUserDefaults so I attempted to convert it to NSData and then use it.
Here's my ViewController.m file:
-(id)initWithCoder:(NSCoder *)aDecoder
{
self=[[calViewController alloc]init];
if (self!=nil) {
self.list=[aDecoder decodeObjectForKey:#"list"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:list forKey:#"list"];
}
And here's my AppDelegate.m file:
-(void) applicationDidFinishLaunching:(UIApplication *)application
{
NSLog(#"Application did finish launching");
defaults=[NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray=[defaults objectForKey:#"lastArray"];
if (dataRepresentingSavedArray!=nil) {
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if (oldSavedArray != nil)
{
listAr = [[NSMutableArray alloc] initWithArray:oldSavedArray];
else
{
listAr = [[NSMutableArray alloc] init];
NSLog(#"listAr: %#",listAr);
}
}
}
-(void) applicationWillTerminate:(UIApplication *)application
{
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:listAr] forKey:#"lastArray"];
}
I've never used UserDefaults before and I absolutely confused. I've read all the docs but I don't seem to be getting it right!
EDIT:
-(void) applicationDidFinishLaunching:(UIApplication *)application
{
NSLog(#"Application did finish launching");
defaults=[NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"lastArray"]) {
NSData *dataRepresentingSavedArray=[defaults objectForKey:#"lastArray"];
if (dataRepresentingSavedArray!=nil) {
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if (oldSavedArray != nil)
listAr = [[NSMutableArray alloc] initWithArray:oldSavedArray];
}
}
else
{
listAr = [[NSMutableArray alloc] init];
NSLog(#"listAr: %#",listAr);
}
}