NSKeyedArchiver archivedDataWithRootObject: - objective-c

Is the parameter for NSKeyedArchiver archivedDataWithRootObject: supposed to be the array I am trying to save, or the array converted into NSData?

Yuji's answer is right. but more accurately, your element of an array have to implement protocol and fillin your own code to methods initWithCoder: and encodeWithCoder:
like:
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
self.title = [decoder decodeObjectForKey:#"title"];
self.author = [decoder decodeObjectForKey:#"author"];
self.published = [decoder decodeBoolForKey:#"published"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:title forKey:#"time"];
[encoder encodeObject:author forKey:#"author"];
[encoder encodeBool:published forKey:#"published"];
}
then you can use the archiver and unchariver like:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:notes];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"notes"];
NSData *notesData = [[NSUserDefaults standardUserDefaults] objectForKey:#"notes"];
NSArray *notes = [NSKeyedUnarchiver unarchiveObjectWithData:notesData];
For more, you can get reference "Archiving Objective-C Objects with NSCoding".

To convert a generic array to an NSData, you need an archiver! If you know how to feed the NSData, you know how to use NSKeyedArchiver. So:
NSArray* array= ... ;
NSData* data=[NSKeyedArchiver archivedDataWithRootObject:array];
Of course all elements in your array needs to implement encodeWithCoder:.

Related

NSMutableArray not saved NSUserDefault

I'm trying to save a NSMutableArray with NSUSerDefault and then open the array.
Here some code:
-(IBAction)btnSave{
Class *aClass = [[Class alloc]init];
aClass.idClass=#"aaaxxx";
aClass.nameClass=#"hello";
[myArray addObject:aClass];
NSUserDefaults *arrayDefault=[NSUserDefaults standardUserDefaults];
[arrayDefault setObject:myArray forKey:#"savedArray"];
[arrayDefault synchronize];
}
and
- (void)viewDidLoad
{
[super viewDidLoad];
myArray=[[NSMutableArray alloc]init];
NSMutableArray *savedArray=[[NSUserDefaults standardUserDefaults]objectForKey:#"savedArray"];
if(savedArray!=NULL){
myArray=savedArray;
[tableView reloadData];
}
// Do any additional setup after loading the view from its nib.
}
when I compile and when I push the button, this is what I read on log output:
[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value '(
"<Class: 0x8452b40>"
)' of class '__NSArrayM'. Note that dictionaries and arrays in property lists must also contain only property values
and obviously when I reopen the view the array is not loaded.
any help?
NSUserDefaults allows only Premitive DataTypes to be store in it. if you want to store your custom class Object then use following code, for more Detail refer this IOS Documentation
//create an array with your custom class objects
Class *aClass = [[Class alloc]init];
aClass.idClass=#"aaaxxx";
aClass.nameClass=#"hello";
[myArray addObject:aClass];
//convert your array to `NSData` object using `NSKeyedArchiver`
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:myArray];
//store it to `NSUserDefaults`
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"myArray"];
[[NSUserDefaults standardUserDefaults] synchronize];
//convert your stored Object back to `NSData` using `NSKeyedUnarchiver`
NSData *storedData = [[NSUserDefaults standardUserDefaults] objectForKey:#"myArray"];
NSArray *storedArr = [NSArray arrayWithArray:[NSKeyedUnarchiver unarchiveObjectWithData:storedData]];
NSLog(#"%#",storedArr[0]);
First you implement NSCoding in you object class. The in order to save/load the NSMutableArray of those objects you can do:
- (void) loadArray {
//Loading the NSMutableArray
NSData *arrayData = [[NSUserDefaults standardUserDefaults] objectForKey:#"ArrayKey"];
myArray = [NSKeyedUnarchiver arrayData];
}
-(void) saveArray {
//Saving the NSMutableArray
NSData *arrayData = [NSKeyedArchiver archivedDataWithRootObject:myArray];
[[NSUserDefaults standardUserDefaults] setObject:arrayData forKey:#"ArrayKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
For more info I suggest looking at a longer tutorial. For example Ray Wenderlich's How to Save your App Data with NSCoding And NSFileManager is a good one.
You need to implement NSCoding protocol in your model object Class. It is needed to serialize and deserialize non standard data to NSUserDefaults.
Now you can use NSKeyedArchiver to convert the object to data and store in userDefaults. Use NSKeyedUnarchiver to convert the data back to the object from defaults.
//h file
#interface Class : NSObject<NSCoding>
#property (nonatomic, copy) NSString *idClass;
#property (nonatomic, copy) NSString *nameClass;
//m File
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.idClass forKey:#"IDClass"];
[aCoder encodeObject:self.nameClass forKey:#"NameClass"];
}
- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super init];
if (self) {
self.idClass = [aDecoder decodeObjectForKey:#"IDClass"];
self.nameClass = [aDecoder decodeObjectForKey:#"NameClass"];
}
return self;
}
After implementing the NSCoding you can archive them to store as data in userDefaults
//Saving array
Class *aClass = [[Class alloc]init];
aClass.idClass=#"aaaxxx";
aClass.nameClass=#"hello";
[myArray addObject:aClass];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:myArray];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:data forKey:#"savedArray"];
[defaults synchronize];
//Retrieving
NSData *data = [defaults objectForKey:#"savedArray"];
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:data];

Save array of objects with properties to plist

I have a Mutable Array containing different objects such as strings, UIImage , etc.
They are sorted like this:
Example:
BugData *bug1 = [[BugData alloc]initWithTitle:#"Spider" rank:#"123" thumbImage:[UIImage imageNamed:#"1.jpeg"]];
...
...
NSMutableArray *bugs = [NSMutableArray arrayWithObjects:bug1,bug2,bug3,bug4, nil];
So basically it's an array with objects with different properties.
I Tried to save a single string to a file with the next code and it's working fine but when I try to save the array with the objects, i get an empty plist file.
NSString *docsDir = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
NSString * path = [docsDir stringByAppendingPathComponent:#"data.plist"];
NSLog(#"%#",bugs); //Making sure the array is full
[bugs writeToFile:path atomically:YES];
What am I doing wrong?
When you write a string or any primitive data to plist it can be saved directly. But when you try to save an object, you need to use NSCoding.
You have to implement two methods encodeWithCoder: to write and initWithCoder: to read it back in your BugData Class.
EDIT:
Something like this :
Change Float to Integer or String or Array as per your requirement and give a suitable key to them.
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:_title forKey:#"title"];
[coder encodeFloat:_rating forKey:#"rating"];
NSData *image = UIImagePNGRepresentation(_thumbImage);
[coder encodeObject:(image) forKey:#"thumbImage"];
}
- (id)initWithCoder:(NSCoder *)coder {
_title = [coder decodeObjectForKey:#"title"];
_rating = [coder decodeFloatForKey:#"rating"];
NSData *image = [coder decodeObjectForKey:#"thumbImage"];
_thumbImage = [UIImage imageWithData:image];
return self;
}
Even this will help you.
Implement NSCoding in your BugData class as below
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeFloat:title forKey:#"title"];
[coder encodeFloat:rank forKey:#"rank"];
[coder encodeObject:UIImagePNGRepresentation(thumbImage) forKey:#"thumbImageData"];
}
- (id)initWithCoder:(NSCoder *)coder {
title = [coder decodeFloatForKey:#"title"];
rank = [coder decodeFloatForKey:#"rank"];
NSData *imgData = [coder decodeObjectForKey:#"thumbImageData"];
thumbImage = [UIImage imageWithData:imgData ];
return self;
}
BugData must implement the NSCoding protocol.You need this method to encode the data:
- (void) encodeWithCoder: (NSCoder*) encoder;
Where you should provide a NSData object representing the class (decode it with the decoder).
To read the plist you need to implement this method:
-(id) initWithCoder: (NSCoder*) decoder;
Where you read data from decoder and return a BugData object.

NSUserDefaults with Custom object that has an array of custom objects

I have an object that I convert into NSData using an NSKeyedArchiver and then store it into NSUserDefaults. Everything gets saved correctly except for the elements of an array that the object has. All the objects in the array have conform to the NSCoder protocols (or whatever theyre called- ex. self.property = [decoder decodeObjectForKey:#"key"] and [encoder encodeObjectForKey:#"key"])
When I save the object, the elements of the array remain in the array, but their properties themselves do not get saved. i DO call the sycnrhonize method, so that is not the issue.
NOTE that all other times i save & load it is correct, it just does not save the elements of an array that belongs to an object. Do i have to save that separately?
The "current status" NSNumber is not being saved. Objective and target are being saved
import "Level.h"
#implementation Level
#synthesize objective = _objective;
#synthesize isComplete = _isComplete;
#synthesize goldReward = _goldReward;
#synthesize xpReward = _xpReward;
#synthesize missionID = _missionID;
#synthesize currentStatus = _currentStatus;
#synthesize targetName = _targetName;
#synthesize owner = _owner;
-(void)dealloc{
[super dealloc];
}
-(id)initWithMissionID:(int)number{
if (self = [super init]) {
self.currentStatus = 0;
self.isComplete = NO;
self.missionID = [NSNumber numberWithInt:number];
[self setUpMisson];
}
return self;
}
-(void)setUpMisson{
if ([self.missionID intValue] == 0) {
self.xpReward = [NSNumber numberWithInt:100];
self.goldReward = [NSNumber numberWithInt:100];
self.objective = [NSNumber numberWithInt:3];
self.targetName = #"Swordsman";
CCLOG(#"Gotta kill some swordsmen!");
}
}
-(void)encodeWithCoder:(NSCoder *)encoder{
[encoder encodeObject:self.objective forKey:#"objective"];
[encoder encodeObject:self.isComplete forKey:#"isComplete"];
[encoder encodeObject:self.goldReward forKey:#"goldReward"];
[encoder encodeObject:self.xpReward forKey:#"xpReward"];
[encoder encodeObject:self.missionID forKey:#"missionID"];
[encoder encodeObject:self.currentStatus forKey:#"currentStatus"];
[encoder encodeObject:self.targetName forKey:#"targetName"];
[encoder encodeObject:self.owner forKey:#"owner"];
CCLOG(#"SAVING LEVEL");
}
-(id)initWithCoder:(NSCoder *)decoder{
if (self = [super init]) {
self.objective = [[decoder decodeObjectForKey:#"objective"]retain];
self.isComplete = [[decoder decodeObjectForKey:#"isComplete"]retain];
self.goldReward = [[decoder decodeObjectForKey:#"goldReward"]retain];
self.xpReward = [[decoder decodeObjectForKey:#"xpReward"]retain];
self.missionID = [[decoder decodeObjectForKey:#"missionID"]retain];
self.targetName = [[decoder decodeObjectForKey:#"targetName"]retain];
self.owner = [[decoder decodeObjectForKey:#"owner"]retain];
CCLOG(#"LOADING LEVEL");
}
return self;
}
-(void)updateStatusForKill:(AI *)killedTarget{
CCLOG(#"WE KILLED: %# and OUR GOAL IS: %#",killedTarget.name,self.targetName);
if ([killedTarget.name isEqualToString:self.targetName]) {
[self setCurrentStatus:[NSNumber numberWithInt:[self.currentStatus intValue]+1]];
CCLOG(#"Current Status: %i Objective: %i", [self.currentStatus intValue],[self.objective intValue]);
if ([self.currentStatus intValue] == [self.objective intValue]) {
[self completeMission];
}
}
}
-(void)completeMission{
[self.owner setCoins:[NSNumber numberWithInt:[[self.owner coins]intValue] + [self.goldReward intValue]]];
[self.owner setXp:[NSNumber numberWithInt:[[self.owner xp]intValue] + [self.xpReward intValue]]];
CCLOG(#"complete");
[[self.owner missionList]removeObject:self];
}
#end
EDIT: The "owner" refers back to the object being saved. I think this is where the problem is, so I'm removing that and testing again.
EDIT: and that did nothing!
What you describe should "just work." In the encodeWithCoder method of your custom object, you would just encode the array object, and that should cause the array and it's contents to be encoded.
However, if any of the objects in the array do not support NSCoding, that will fail. My guess is that something in your array (or it's children or grandchildren) does not support NSCoding.
I ran into this problem when trying to save an array of Accounts that contain property values and another custom object. I couldn't save my data with your proposed solution because I was arbitrarily adding accounts to an array, and it wouldn't make sense to come up with unique identifiers for dynamically added accounts. I ended up nesting the NSCoding protocol:
In my AccountManager class:
- (void) saveAllAccounts {
[self deleteAllAccounts]; //Just removes whatever was previously stored there
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:accountArray] forKey:saveAccountsArrayKey];
[[NSUserDefaults standardUserDefaults] synchronize];
}
In my Account class:
- (void) encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:username forKey:#"username"];
[aCoder encodeObject:token forKey:#"token"];
[aCoder encodeObject:specialID forKey:#"special ID"];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:deviceArray];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"device array"];
}
- (id) initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self != nil) {
username = [[aDecoder decodeObjectForKey:#"username"] retain];
token = [[aDecoder decodeObjectForKey:#"token"] retain];
ecoID = [[aDecoder decodeObjectForKey:#"eco ID"] retain];
NSData *deviceArrayData = [[NSUserDefaults standardUserDefaults] objectForKey:#"device array"];
if (deviceArrayData != nil) {
NSArray *savedArray = [NSKeyedUnarchiver unarchiveObjectWithData: deviceArrayData];
if (savedArray != nil)
deviceArray = [[NSMutableArray alloc] initWithArray:savedArray];
}
}
return self;
}
In my AccountDevice class:
- (void) encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:pairingPassword forKey:#"pairing password"];
[aCoder encodeObject:devicePin forKey:#"device pin"];
}
- (id) initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self != nil) {
password = [[aDecoder decodeObjectForKey:#"password"] retain];
pin = [[aDecoder decodeObjectForKey:#"pin"] retain];
}
return self;
}
It might be a little buggy because I haven't finished testing it, but my preliminary tests were successful, and I think the concept is there.
It looks like you're using -encodeObject:forKey: and -decodeObjectForKey: even on values that aren't objects. For example, in your -initWithMissionID: you've got:
self.isComplete = NO;
which makes me think that complete is a BOOL property, but your -encodeObject:forKey: says:
[encoder encodeObject:self.isComplete forKey:#"isComplete"];
I think you probably want to call -encodeBool:forKey: instead, like this:
[encoder encodeBool:self.isComplete forKey:#"isComplete"];
On the other hand, I'd really expect some sort of warning if the problem were that simple. Do you get any warnings? It's harder to infer the types of your other properties, but look at each of your properties for the same kind of problem.
found a workaround using a Unique-Id system for each mission and saving the progress for each mission separately into NSUserDefaults which are just then loaded again. Not ideal, but it works. Thanks for everyone's help!

Storing Custom Object in NSMutableDictionary

I am trying to store a custom object in NSMutableDictionary. After saving when I read the object from NSMutableDictionary it's always null.
Here is the code
//Saving
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
CustomObject *obj1 = [[CustomObject alloc] init];
obj1.property1 = #"My First Property";
[dict setObject:obj1 forKey:#"FirstObjectKey"];
[dict writeToFile:[self dataFilePath] atomically:YES];
// Reading
NSString *filePath = [self dataFilePath];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithContentsOfFile:filePath];
CustomObject *tempObj = [dict objectForKey:#"FirstObjectKey"];
NSLog(#"Object %#", tempObj);
NSLog(#"property1:%#,,tempObj.property1);
How can I store a custom class object in NSMutableDictionary?
The problem is not with putting the object into the dictionary; the problem is with writing it to a file.
Your custom class has to be serializable. You need to implement the NSCoding protocol so that Cocoa knows what to do with your class when you ask for it to be written out to disk.
This is pretty simple to do; you need to implement two methods that will look something like the following:
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
// If inheriting from a class that implements initWithCoder:
// self = [super initWithCoder:coder];
myFirstIvar = [[coder decodeObjectForKey:#"myFirstIvar] retain];
mySecondIvar = [[coder decodeObjectForKey:#"mySecondIvar] retain];
// etc.
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
// If inheriting from a class that implements encodeWithCoder:
// [super encodeWithCoder:coder];
[coder encodeObject:myFirstIvar forKey:#"myFirstIvar"];
[coder encodeObject:mySecondIvar forKey:#"mySecondIvar"];
// etc.
}
Essentially you're just listing the ivars that you need to save, and then reading them back in properly.
UPDATE: As mentioned by Eimantas, you'll also need NSKeyedArchiver. To save:
NSData * myData = [NSKeyedArchiver archivedDataWithRootObject:myDict];
BOOL result = [myData writeToFile:[self dataFilePath] atomically:YES];
To reload:
NSData * myData = [NSData dataWithContentsOfFile:[self dataFilePath]];
NSDictionary * myDict = [NSKeyedUnarchiver unarchiveObjectWithData:myData];
I think that should do it.
writeToFile method can store only standard types of objects into plist. If you have custom object you'd have to use NSKeyedArchiver/NSKeyedUnarchiver for this.

NSKeyedUnarchiver objects getting broken?

I have an array that I'm saving to NSUserDefaults, containing an array of my custom class Assignment, which conforms to the NSCoding protocol. The array saves and loads properly, and I can verify that the retrieved first object of the array is of the class Assignment. The problem happens when I try to access ivars of the Assignment object in the array. It crashes and I get the following error:
*** -[CFString respondsToSelector:]: message sent to deallocated instance 0x3948d60
Here is the code I'm using to save to user defaults. Note that I am also retrieving and checking the saved object for debugging purposes.
-(void)saveToUserDefaults:(NSArray*)myArray
{
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
if (standardUserDefaults) {
[standardUserDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:myArray] forKey:#"Assignments"];
[standardUserDefaults synchronize];
}
NSLog(#"Assignments array saved. (%d assignments in array)",[myArray count]);
NSData *dataCheck = [[NSData alloc] initWithData:[standardUserDefaults objectForKey:#"Assignments"]];
NSArray *arrayCheck = [[NSArray alloc] initWithArray:[NSKeyedUnarchiver unarchiveObjectWithData:dataCheck]];
NSLog(#"Checking saved array (%d assignments in array)",[arrayCheck count]);
if ([[arrayCheck objectAtIndex:0] isKindOfClass:[Assignment class]]) {
NSLog(#"It's of the class Assignment.");
}
Assignment *testAssignment = [[Assignment alloc] initWithAssignment:[arrayCheck objectAtIndex:0]];
NSLog(#"Title: %# Course: %#",[testAssignment title],[testAssignment course]);
}
Everything is fine until I allocate testAssignment, which is where the crash happens. Does anyone have any ideas?
EDIT: Here are my NSCoding methods in the Assignment class:
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:title forKey:#"title"];
[coder encodeObject:course forKey:#"course"];
[coder encodeObject:dueDate forKey:#"dueDate"];
[coder encodeObject:notes forKey:#"notes"];
}
- (id)initWithCoder:(NSCoder *)coder {
self = [[Assignment alloc] init];
if (self != nil)
{
title = [coder decodeObjectForKey:#"title"];
course = [coder decodeObjectForKey:#"course"];
dueDate = [coder decodeObjectForKey:#"dueDate"];
notes = [coder decodeObjectForKey:#"notes"];
}
return self;
}
Answered my own question. In initWithCoder, I needed to retain all of the objects I was decoding:
//Example
title = [[coder decodeObjectForKey:#"title"] retain];
Everything works beautifully now. :)