Saving data with NSMutableDictionary - objective-c

I had a method to save a dic to the disk:
+(BOOL) writeApplicationData:(NSDictionary *)data
bwriteFileName:(NSString *)fileName
{
NSLog(#"writeApplicationData");
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
if (!documentsDirectory) {
NSLog(#"Documents directory not found!");
return NO;
}
NSString *appFile = [documentsDirectory stringByAppendingPathComponent:fileName];
return ([data writeToFile:appFile atomically:YES]);
}
And I tested it with:
NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
NSMutableDictionary *d1 = [[NSMutableDictionary alloc] init];
NSMutableDictionary *d2 = [[NSMutableDictionary alloc] init];
[d1 setObject:#"d11"
forKey:#"d11"];
[d1 setObject:#"d12"
forKey:#"d12"];
[d1 setObject:#"d13"
forKey:#"d13"];
[d2 setObject:#"d21"
forKey:#"d21"];
[d2 setObject:#"d22"
forKey:#"d22"];
[d2 setObject:#"d23"
forKey:#"d23"];
[dic setObject:d1
forKey:#"d1"];
[dic setObject:d2
forKey:#"d2"];
[self writeApplicationData:dic
bwriteFileName:#"testSave"];
And the data is saved correctly.
Then I tried to save d1 with class obj in it:
LevelInfoData *levelInfoData = [[LevelInfoData alloc] init];
[levelInfoDictionary setObject:levelInfoData
forKey:#"test"];
[dic setObject:levelInfoDictionary
forKey:#"LevelInfoDictionary"];
But this time, even no plist file was generated in the disk.
Here is the LevelInfoData class:
#interface LevelInfoData : NSObject {
int levelNum;
}
#property (nonatomic) int levelNum;
#end
#implementation LevelInfoData
#synthesize levelNum;
#synthesize isLevelLocked;
#synthesize isLevelCleared;
#synthesize levelHighScore;
-(id)init
{
if( (self = [super init]) ) {
levelNum = 0;
}
return self;
}
#end
I'm really confused, hope somebody could help me out, thanks.

The contents of the dictionary need to be property list type objects.
From the NSDictionary Class Reference:
This method recursively validates that all the contained objects are property list objects (instances of NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary) before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.
https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/Reference/Reference.html
You may want to try making your custom class a subclass of NSData rather than NSObject.

I'm not sure how attached you are to NSDictionary, but this may be a situation where NSCoder will better serve you.
See nscoder vs nsdictionary when do you use what
More details here:
NSCoder Class Reference
Some code snippets
A tutorial

Related

Serialization Objective C not working

I'm trying to make an app with Objective C.
I'm trying to serialise an array existing out of objects and after wards deserialise it. Inside the object there are the methods
(id)initWithCoder:(NSCoder *)coder` and `encodeWithCoder:(NSCoder *)coder
But it seems the "rootObject" stays "nil" in the "loadDataFromDisk" -method
Here is my code :
#import "Alarm.h"
#implementation Alarm
#synthesize array = _array;
#synthesize time = _time;
#synthesize coder = _coder;
-(id)initWithCoder:(NSCoder *)coder
{
self = [super init];
if(self)
{
_array = [coder decodeObjectForKey:#"array"];
_time = [coder decodeObjectForKey:#"time"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.array forKey:#"array"];
[coder encodeObject:self.time forKey:#"time"];
}
#end
My save and load methods :
-(void)saveDataToDisk
{
NSString * path = [self pathForDataFile];
NSLog(#"Writing alarms to '%#' %lu", path, (unsigned long)array.count);
NSMutableDictionary * rootObject;
rootObject = [NSMutableDictionary dictionary];
[rootObject setValue:array forKey:#"alarms"];
[NSKeyedArchiver archiveRootObject:rootObject toFile:path];
}
-(void)loadDataFromDisk
{
NSString *path = [self pathForDataFile];
NSDictionary *rootObject = [NSMutableDictionary dictionary];
rootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
// "array" is an array with Objects of "Alarm"
array = [rootObject valueForKey:#"alarms"];
NSLog(#"Loaded from : %# %lu",path ,(unsigned long)array.count);
}
I hope anyone can help me out with this.
Thanks in advance.
You #synthized the array backing store (ivar) as _array. So you need to access the array as either _array or self.array. In saveDataToDisk and loadDataFromDisk it is accessed as array.
To test your array coding try something simple like this:
NSLog(#"array: %#", self.array);
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.array];
NSLog(#"data: %#", data);
NSArray *recovered = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSLog(#"recovered: %#", recovered);
Note: There is no need to wrap your array in a NSMutableDictionary.
When that works change it to the file based method calls.
Check that the filePath is valid.
Check that the file is created.
Check that the file contents are the same as in the above test code.
Note: There is no reason to wrap your array in a NSMutableDictionary.

NSDictionary value replacing instead of adding in Plist

hi i tried to add values(book id,page number,notes) from NSdictionary to Plist but each time the new value replacing the previous one?but i need all values in plist my code for adding dictionary to plist is
NSString *bid=#"95";
NSString *pnum=#"12";
userNotes=[[NSMutableDictionary alloc]init];
[userNotes setValue:userNotesTextview.text forKey:#"notes"];
[userNotes setValue:bid forKey:#"bookid"];
[userNotes setValue:pnum forKey:#"pagenumber"];
userNotesView.hidden=YES;
_background.hidden = YES;
userNotesTextview.text=#"";
[self savingMetaData];
NSMutableArray *notes=[[NSMutableArray alloc]init];
[notes addObject:userNotes];
NSMutableDictionary *final=[[NSMutableDictionary alloc]init];
[final setValue:notes forKey:#"usernotes"];
[final writeToFile:metaDataPath atomically:YES];
and my plist look like
how can i solve this problem
Fetch the existing array from the plist as below, but first make sure you have copied you plist to Documents directory or, to some writable folder as below
NSFileManager *fileManager=[NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *docPath=[[paths objectAtIndex:0] stringByAppendingString:#"yourplist.plist"];
BOOL fileExists = [fileManager fileExistsAtPath: docPath];
NSError *error = nil;
if(!fileExists)
{
NSString *strSourcePath = [[NSBundle mainBundle] pathForResource:#"yourplist" ofType:#"plist"];
[fileManager copyItemAtPath:strSourcePath toPath:docPath error:&error];
}
NSString *path = docPath;
NSMutableDictionary *plistdictionary = [[NSMutableDictionary alloc]initWithContentsOfFile:path];
NSMutableArray *notes=[plistdictionary objectForKey:#"usernotes"];
if(notes==nil){
notes=[[NSMutableArray alloc] init];
}
NSString *bid=#"95";
NSString *pnum=#"12";
userNotes=[[NSMutableDictionary alloc]init];
[userNotes setValue:userNotesTextview.text forKey:#"notes"];
[userNotes setValue:bid forKey:#"bookid"];
[userNotes setValue:pnum forKey:#"pagenumber"];
[notes addObject:userNotes];
then finally
NSMutableDictionary *final=[[NSMutableDictionary alloc]init];
[final setValue:notes forKey:#"usernotes"];
[final writeToFile:docPath atomically:YES];
Note: You cannot write anything in MainBundle, so better to copy your plist to Documents directory and use from there..
because plist can store value with unique key only. if you try to save value with same key it will replace old one with new value. so always save new value with new key (eg. item0, item1, item3 etc.)
following line will store two usernote with key #"usernotes1" and #"usernotes2" respectively
[final setValue:notes forKey:#"usernotes1"];
[final setValue:notes forKey:#"usernotes2"];
Plist structure looks like this
You can create a UserNote model class.
#define kBookID #"bookid"
#define kPageNumber #"pageNumber"
#define kNotes #"notes"
#interface UserNote : NSObject
#property (nonatomic, copy) NSString *bookID;
#property (nonatomic, copy) NSString *pageNumber;
#property (nonatomic, copy) NSString *notes;
- (id)initWithDictionary:(NSDictionary *)dictionary;
+ (NSArray *)savedUserNotes;
- (void)save;
#end
Initialize
- (id)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self)
{
self.bookID = dictionary[kBookID];
self.pageNumber = dictionary[kPageNumber];
self.notes = dictionary[kNotes];
}
return self;
}
Find the document path of plist file in documents directory. If the plist file is not there create a new one and return the path.
+ (NSString *)userNotesDocumentPath
{
NSString *documentsPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:#"UserNotes.plist"];
NSFileManager *fileManger = [NSFileManager defaultManager];
if (![fileManger fileExistsAtPath:documentsPath])
{
NSString *bundleResourcePath = [[NSBundle mainBundle]pathForResource:#"UserNotes" ofType:#"plist"];
NSArray *userNotes = [NSArray arrayWithContentsOfFile:bundleResourcePath];
[userNotes writeToFile:documentsPath atomically:YES];
}
return documentsPath;
}
Fetches all saved usernotes from plist file.
+ (NSArray *)savedUserNotes
{
NSString *documentsPath = [self userNotesDocumentPath];
NSArray *savedNotes = [NSArray arrayWithContentsOfFile:documentsPath];
NSMutableArray *savedUserNotes = [#[] mutableCopy];
for (NSDictionary *dict in savedNotes) {
UserNote *note = [[UserNote alloc]initWithDictionary:dict];
[savedUserNotes addObject:note];
}
return savedUserNotes;
}
Saves a usenote to plist
- (NSDictionary *)userNoteDictionary
{
return #{kBookID:self.bookID,kPageNumber:self.pageNumber,kNotes:self.notes};
}
- (void)saveUserNotesToPlist:(NSArray *)userNotes
{
NSMutableArray *mutableUserNotes = [#[] mutableCopy];
for (UserNote *note in userNotes) {
NSDictionary *dict = [note userNoteDictionary];
[mutableUserNotes addObject:dict];
}
NSString *documentsPath = [UserNote userNotesDocumentPath];
[mutableUserNotes writeToFile:documentsPath atomically:YES];
}
#pragma mark - Save
- (void)save
{
NSMutableArray *savedNotes = [[UserNote savedUserNotes] mutableCopy];
[savedNotes addObject:self];
[self saveUserNotesToPlist:savedNotes];
}
In you viewController where user makes a note
- (IBAction)saveUserNoteButtonPressed:(UIButton *)button
{
UserNote *note = [UserNote new];
note.bookID = #"95";
note.pageNumber = #"12";
note.notes = self.userNotesTextview.text;
[note save];
}
Demo Source Code

Can't save to plist

I have my own object class from inherited from NSObject
#interface BlockedCell : NSObject
{
NSValue *gridValue;
NSString *name;
}
#property (nonatomic, retain) NSValue *gridValue;
#property (nonatomic, retain) NSString *name;
#end
So I try to create a few objects:
NSMutableDictionary *dict = [[dict alloc] init];
for (int i = 0; i < 5; ++i)
{
BlockedCell *block = [[BlockedCell alloc] init];
block.gridValue = [NSValue valueWithCGPoint: CGPointMake(0.0f, 0.0f)];
block.name = #"something";
[dict setObject: block forKey: [NSString stringWithFormat: #"item_%d", i]];
[block release];
}
if([dict writeToFile: path atomic: YES])
NSLog(#"Saved");
else
NSLog(#"Failed to save");
[dict release];
And what I get for the output is "Failed to save"..
If my dictionary does not contains any data, then it will output "Saved"
EDIT:
After I did more testing, I found out that actually is the NSValue causing the saving failed.
So what should I do if I want to save CGPoint into plist?
As you discovered, property lists cannot store NSValue objects directly. The supported classes are NSData, NSString, NSArray, NSDictionary, NSDate, and NSNumber, as documented in the NSPropertyListSerialization Class Reference.
The easiest workaround would be to use NSString instead of NSValue:
block.gridString = NSStringFromCGPoint(CGPointZero);
CGPoint point = CGPointFromString(block.gridString);
you cant save NSValue directly.In your case you have to save a point in the form ofstring use below line
CGPoint point = CGPointMake(10.0,20.0)
//you need to translate the point into a compatible "Plist" object such as NSString
//luckily, there is a method for that
[rectArray addObject:NSStringFromPoint(point)];
//save into a plist
.....
on retrieval of this value
CGPoint Point = CGPointFromString([rectArray objectAtIndex:0]);

Write custom object to .plist in Cocoa

I am blocking into something and I am sure it is too big.
I have a custom object that look like this
#interface DownloadObject : NSObject <NSCoding>{
NSNumber *key;
NSString *name;
NSNumber *progress;
NSNumber *progressBytes;
NSNumber *size;
NSString *path;
}
#property (copy) NSNumber *key;
#property (copy) NSString *name;
#property (copy) NSNumber *progress;
#property (copy) NSNumber *size;
#property (copy) NSString *path;
#property (copy) NSNumber *progressBytes;
-(id)initWithKey:(NSNumber *)k name:(NSString *)n progress:(NSNumber *)pro size:(NSNumber *)s path:(NSString *)p progressBytes:(NSNumber *)pb;
#end
And the implementation
#implementation DownloadObject
#synthesize size, progress, name, key, path, progressBytes;
-(id)initWithKey:(NSNumber *)k name:(NSString *)n progress:(NSNumber *)pro size:(NSNumber *)s path:(NSString *)p progressBytes:(NSNumber *)pb
{
self.key = k;
self.name = n;
self.progress = pro;
self.size = s;
self.path = p;
self.progressBytes = pb;
return self;
}
-(id) initWithCoder: (NSCoder*) coder {
if (self = [super init]) {
self.key = [[coder decodeObjectForKey:#"Key"] retain];
self.name = [[coder decodeObjectForKey:#"Name"] retain];
self.progress = [[coder decodeObjectForKey:#"Progress"] retain];
self.size = [[coder decodeObjectForKey:#"Size"] retain];
self.path = [[coder decodeObjectForKey:#"Path"] retain];
self.progressBytes = [[coder decodeObjectForKey:#"ProgressBytes"]retain];
}
return self;
}
-(void) encodeWithCoder: (NSCoder*) coder {
[coder encodeObject:self.key forKey:#"Key"];
[coder encodeObject:self.name forKey:#"Name"];
[coder encodeObject:self.progress forKey:#"Progress"];
[coder encodeObject:self.size forKey:#"Size"];
[coder encodeObject:self.path forKey:#"Path"];
[coder encodeObject:self.progressBytes forKey:#"ProgressBytes"];
}
-(void)dealloc
{
[key release];
[name release];
[size release];
[progress release];
[path release];
[progressBytes release];
[super dealloc];
}
#end
As you can see it implement NSCoding (I think so, NSObject does not conform to NSCoding). Now when I try to do something like that just to test
downloadArray = [[[NSMutableArray alloc]init]retain];
NSNumber *number = [NSNumber numberWithInt:10];
DownloadObject *object = [[DownloadObject alloc]initWithKey:number name:#"hey" progress:number size:number path:#"hey" progressBytes:number];
[downloadArray addObject:object];
[object release];
[downloadArray writeToFile:path atomically:YES];
downloadArray is a NSMutableArray. My plist read/write is fine, the path is located in the application support and when I log it show the plist path.
But it just does not write the array to the plist, any idea ?
Property list files can only store basic data types and cannot contain custom objects. You need to convert your object to an NSData object if you want it to be written to the plist. You can do this with NSKeyedArchiver, which will encode an object which conforms to the NSCoding protocol into an NSData object.
DownloadObject *object = [[DownloadObject alloc]initWithKey:number name:#"hey" progress:number size:number path:#"hey" progressBytes:number];
NSData* objData = [NSKeyedArchiver archivedDataWithRootObject:object];
[downloadArray addObject:objData];
[object release];
When you want to reconstruct your object from the NSData object, you use NSKeyedUnarchiver:
NSData* objData = [downloadArray objectAtIndex:0];
DownloadObject* object = [NSKeyedUnarchiver unarchiveObjectWithData:objData];
You also have several memory leaks in your code. In your -initWithCoder: method, you should not be using accessors to set the value of the ivars, you should just set the ivars directly, like so:
key = [[coder decodeObjectForKey:#"Key"] copy];
You are calling -retain and then using the accessor which is specified as copy, which will mean your object has a retain count of 2 and will not be released. In general you should avoid using accessors in init methods.
Also, in the code where you allocate your downloadArray object, you are calling -alloc and then -retain on the object, which will leave it with a retainCount of 2. You should re-read the Objective-C Memory Management Guidelines.
This works for me:
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:highScoreArray forKey:kHighScoreArrayKey];
[archiver finishEncoding];
[data writeToFile:[self dataFilePath] atomically:YES];
[data release];
[archiver release];
BOOL flag = false;
ObjectFileClass *obj = [yourMutableArray objectAtIndex:0];
//TO Write Data . . .
NSData* archiveData = [NSKeyedArchiver archivedDataWithRootObject:obj.title];
flag =[archiveData writeToFile:path options:NSDataWritingAtomic error:&error];
}
if (flag) {
NSLog(#"Written");
//To Read Data . . .
NSData *archiveData = [NSData dataWithContentsOfFile:path];
id yourClassInstance = [NSKeyedUnarchiver unarchiveObjectWithData:archiveData]; // choose the type of your class instance . . .
NSLog(#"%#",yourClassInstance);
}else{
NSLog(#"Not Written");
}

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.