What is the best way to save an NSMutableArray to NSUserDefaults? - iphone

I have a custom object called Occasion defined as follows:
#import <Foundation/Foundation.h>
#interface Occasion : NSObject {
NSString *_title;
NSDate *_date;
NSString *_imagePath;
}
#property (nonatomic, retain) NSString *title;
#property (nonatomic, retain) NSDate *date;
#property (nonatomic, retain) NSString *imagePath;
Now I have an NSMutableArray of Occasions which I want to save to NSUserDefaults. I know it's not possible in a straight forward fashion so I'm wondering which is the easiest way to do that? If serialization is the answer, then how? Because I read the docs but couldn't understand the way it works fully.

You should use something like NSKeyedArchiver to serialize the array to an NSData, save it to the NSUserDefaults and then use NSKeyedUnarchiver to deserialize it later:
NSData *serialized = [NSKeyedArchiver archivedDataWithRootObject:myArray];
[[NSUserDefaults standardUserDefaults] setObject:serialized forKey:#"myKey"];
//...
NSData *serialized = [[NSUserDefaults standardUserDefaults] objectForKey:#"myKey"];
NSArray *myArray = [NSKeyedUnarchiver unarchiveObjectWithData:serialized];
You will need to implement the NSCoding protocol in your Occasion class and correctly save the various properties to make this work correctly. For more information see the Archives and Serializations Programming Guide. It shouldn't be more than a few lines of code to do this. Something like:
- (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder];
[coder encodeObject:_title forKey:#"_title"];
[coder encodeObject:_date forKey:#"_date"];
[coder encodeObject:_imagePath forKey:#"_imagePath"];
}
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
_title = [[coder decodeObjectForKey:#"_title"] retain];
_date = [[coder decodeObjectForKey:#"_date"] retain];
_imagePath = [[coder decodeObjectForKey:#"_imagePath"] retain];
return self;
}

NSUserDefaults is intended for user preferences, not storing application data. Use CoreData or serialize the objects into the documents directory. You'll need to have your class implement the NSCoding protocol for it to work.
1) Implement NSCoding in Occasion.h
#interface Occasion : NSObject <NSCoding>
2) Implement the protocol in Occasion.m
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.title = [aDecoder decodeObjectForKey:#"title"];
self.date = [aDecoder decodeObjectForKey:#"date"];
self.imagePath = [aDecoder decodeObjectForKey:#"imagePath"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:title forKey:#"title"];
[aCoder encodeObject:date forKey:#"date"];
[aCoder encodeObject:imagePath forKey:#"imagePath"];
}
3) Archive the data to a file in documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *path= [documentsPath stringByAppendingPathComponent:#“occasions”];
[NSKeyedArchiver archiveRootObject:occasions toFile:path];
4) To unarchive...
NSMutableArray *occasions = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

You could implement NSCoding in Occasion.
You then use [NSKeyedArchiver archivedDataWithRootObject:myArray] to create an NSData object from the array. You can put this into user defaults.

Related

NSUserDefaults fail to save NSMutableDictionary

Iam trying to save NSMutableDictionary to NSUserDefaults with this code:
NSUserDefaults *def = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *repositoryData = [[NSMutableDictionary alloc] init];
[repositoryData setObject:personData forKey:#"persondata"];
[def setObject:repositoryData forKey:#"kUserRepository"];
[def synchronize];
[repositoryData removeAllObjects];
[repositoryData release];
But i am getting this msg:
[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value '{
persondata = "<Person: 0x1ed57e70>";
}' of class '__NSDictionaryM'. Note that dictionaries and arrays in property lists must also contain only property values.
and also the Person Class have NSCoding:
Person.h
#interface Person : NSObject <NSCoding> {
NSString *name;
NSString *email;
NSString *phoneNumber;
NSString *gender;
int age;
}
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSString *email;
#property (nonatomic, retain) NSString *phoneNumber;
#property (nonatomic, retain) NSString *gender;
#property (assign) int age;
#end
Person.m
#implementation Person
#synthesize name,email,phoneNumber,age,gender;
#pragma mark
#pragma mark NSCoder
- (void)encodeWithCoder:(NSCoder *)encoder{
[encoder encodeObject:self.name forKey:#"username"];
[encoder encodeObject:self.email forKey:#"useremail"];
[encoder encodeObject:self.phoneNumber forKey:#"userphonenumber"];
[encoder encodeObject:self.gender forKey:#"usergender"];
[encoder encodeInt:self.age forKey:#"userage"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if(self = [super init]){
self.name = [decoder decodeObjectForKey:#"username"];
self.email = [decoder decodeObjectForKey:#"useremail"];
self.phoneNumber = [decoder decodeObjectForKey:#"userphonenumber"];
self.gender = [decoder decodeObjectForKey:#"usergender"];
self.age = [decoder decodeIntForKey:#"userage"];
}
return self;
}
#end
Any idea why it happen?
There are very specific restrictions as to what type of data can be synced to NSUserDefaults within an NSDictionary. These include NSString, NSNumber, NSData, NSArray and NSDictionary. Use your NSCoder methods to serialize to NSData (using NSKeyedArchiver) before storing in the NSUserDefaults.
Also, take a look at this

how to maintain a plist so you dont lose your values

I have a plist which I read from my bundle into a new plist object that I put in the root directory for reading and writing. My question is what do I do with this or what dose the application do when its quit, and better yet what happens when the app is killed from the "multi task" menu from ios.
Also is there a way to save this plist to memory/the bundle for future use when the application is used again.
My code is as follows for refrence.
Here is my .h
#import <Foundation/Foundation.h>
#interface EngineProperties : NSObject {
NSString *signature;
NSNumber *version;
NSNumber *request;
NSNumber *dataVersion;
NSMutableDictionary *cacheValue;
//cachevalue Items
NSNumber *man;
NSNumber *mod;
NSNumber *sub;
}
#property (copy, nonatomic) NSString *signature;
#property (copy, nonatomic) NSNumber *version;
#property (copy, nonatomic) NSNumber *request;
#property (copy, nonatomic) NSNumber *dataVersion;
#property (copy, nonatomic) NSMutableDictionary *cacheValue;
//cachevalue Items
#property (copy, nonatomic) NSNumber *man;
#property (copy, nonatomic) NSNumber *mod;
#property (copy, nonatomic) NSNumber *sub;
//Singletton
+ (id)sharedManager;
- (void) saveData:(NSString *)methodName signature:(NSString *)pSignature Version:(NSNumber *)pVersion request:(NSNumber *)rNumber dataVersion:(NSNumber *)dvReturned cacheValue:(NSNumber *)cValue;
#end
and Here is my .m
#import "EngineProperties.h"
static EnginePropertiesController *sharedMyManager = nil;
#implementation EngineProperties
#synthesize signature;
#synthesize version;
#synthesize request;
#synthesize dataVersion;
#synthesize cacheValue;
#synthesize man;
#synthesize mod;
#synthesize sub;
#pragma mark Singleton Methods
+ (id)sharedManager {
#synchronized(self) {
if (sharedMyManager == nil)
sharedMyManager = [[self alloc] init];
}
return sharedMyManager;
}
- (id)init {
if (self = [super init]) {
// Data.plist code
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"EngineProperties.plist"];
// check to see if Data.plist exists in documents
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
{
// if not in documents, get property list from main bundle
plistPath = [[NSBundle mainBundle] pathForResource:#"EngineProperties" ofType:#"plist"];
}
// read property list into memory as an NSData object
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSString *errorDesc = nil;
NSPropertyListFormat format;
// convert static property liost into dictionary object
NSDictionary *tempRoot = (NSMutableDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
if (!tempRoot)
{
NSLog(#"Error reading plist: %#, format: %d", errorDesc, format);
}
// assign values
self.signature = [tempRoot objectForKey:#"Signature"];
self.version = [tempRoot objectForKey:#"Version"];
self.request = [tempRoot objectForKey:#"Request"];
self.dataVersion = [tempRoot objectForKey:#"Data Version"];
man = [cacheValue objectForKey:#"Man"];
mod = [cacheValue objectForKey:#"Mod"];
sub = [cacheValue objectForKey:#"SubMod"];
cacheValue = [tempRoot objectForKey:#"Cache Value"];
}
- (void) saveData:(NSString *)methodName signature:(NSString *)pSignature Version:(NSNumber *)pVersion request:(NSNumber *)rNumber dataVersion:(NSNumber *)dvReturned cacheValue:(NSNumber *)cValue;
{
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"EngineProperties.plist"];
// set the variables to the values in the text fields
self.signature = pSignature;
self.version = pVersion;
self.request = rNumber;
self.dataVersion = dvReturned;
//do some if statment stuff here to put the cache in the right place or what have you.
if (methodName == #"manufacturers")
{
self.man = cValue;
}
else if (methodName == #"models")
{
self.mod = cValue;
}
else if (methodName == #"subMod")
{
self.sub = cValue;
}
self.cacheValue = [NSDictionary dictionaryWithObjectsAndKeys:
man, #"Manufacturers",
mod, #"Models",
sub, #"SubModels", nil];
NSDictionary *plistDict = [NSDictionary dictionaryWithObjectsAndKeys:
signature, #"Signature",
version, #"Version",
request, #"Request",
dataVersion, #"Data Version",
cacheValue, #"Cache Value", nil];
NSString *error = nil;
// create NSData from dictionary
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
// check is plistData exists
if(plistData)
{
// write plistData to our Data.plist file
[plistData writeToFile:plistPath atomically:YES];
NSString *myString = [[NSString alloc] initWithData:plistData encoding:NSUTF8StringEncoding];
// NSLog(#"%#", myString);
}
else
{
NSLog(#"Error in saveData: %#", error);
// [error release];
}
}
#end
It honestly depends on how frequently you'll be asking for and changing the plist's values. For instance, my app only needed to retrieve it once, then write it a few times (nothing much), so all of my saving code was at the end of said particular method.
However, if your plist is live (constant value changes), keeping a reference to the data you wish to save that is accessible from the AppDelegate is recommended. That way, you can simply call: beginBackgroundTaskWithExpirationHandler: on -applicationDidResignActive and save your plist data.
(Note, if the user is fast enough to kill your app before it saves completely (big if), there are no guarantees as to the integrity of your plist).
follow the blow link in that link they provided the code also. How to Write/Read data to .plist file.
Plist
You can quickly and easily save plists to NSUserDefaults
Check out this quick tutorial:
http://www.cocoadev.com/index.pl?NSUserDefaults

Leaky Custom Object for storing data from a plist

I have made a very simple custom object pictureData.
Here is the .h file
#import <Foundation/Foundation.h>
#interface pictureData : NSObject {
NSString *fileName;
NSString *photographer;
NSString *title;
NSString *license;
}
#property (nonatomic, retain) NSString *fileName;
#property (nonatomic, retain) NSString *photographer;
#property (nonatomic, retain) NSString *title;
#property (nonatomic, retain) NSString *license;
+(pictureData*)picDataWith:(NSDictionary*)dictionary;
#end
The .m file
#import "pictureData.h"
#implementation pictureData
#synthesize fileName;
#synthesize photographer;
#synthesize title;
#synthesize license;
+ (pictureData*)picDataWith:(NSDictionary *)dictionary {
pictureData *tmp = [[[pictureData alloc] init] autorelease];
tmp.fileName = [dictionary objectForKey:#"fileName"];
tmp.photographer = [dictionary objectForKey:#"photographer"];
tmp.title = [dictionary objectForKey:#"title"];
tmp.license = [dictionary objectForKey:#"license"];
return tmp;
}
-(void)dealloc {
[fileName release];
[photographer release];
[title release];
[license release];
}
#end
I then set up these objects in an array, like so:
NSString *path = [[NSBundle mainBundle] pathForResource:#"pictureLicenses" ofType:#"plist"];
NSArray *tmpDataSource = [NSArray arrayWithContentsOfFile:path];
NSMutableArray *tmp = [[NSMutableArray alloc] init];
self.dataSource = tmp;
[tmp release];
for (NSDictionary *dict in tmpDataSource) {
pictureData *pic = [pictureData picDataWith:dict];
NSLog(#"%#", pic.title);
[self.dataSource addObject:pic];
}
Everything works smashingly. I have a table view which loads the proper picture images, and information, no problem. Upon running Instruments for leaks, I see that my pictureData object is leaks with every allocation.
I would assume that with having my object autoreleased I would not have to worry about manually allocating and deallocating them.
Perhaps is my issue that I use autorelease, which the autoReleasePool keeps a retain count of +1 and then when I add a pictureData object to my array, that also retains it? Thank you all for your time!
edit: Don't forget to call super! Thank you Sam!
Change dealloc to:
-(void)dealloc {
[fileName release];
[photographer release];
[title release];
[license release];
[super dealloc];
}
(call [super dealloc])
In your function, change the return value to include autorelease, like
+ (pictureData*)picDataWith:(NSDictionary *)dictionary
{
...
...
return [tmp autorelease];
}
When you add pictureData object to dataSource, you increase the retain count, so you should autorelease it while returning.
Hope it helps.

EXC_BAD_ACCESS on a NSMutableArray that contains a custom class

I receive I EXC_BAD_ACCESS but i can't understand why.
this is the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
TDBadgedCell *cell = [[[TDBadgedCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
SubscriptionArray * element =[reader.subscriptions objectAtIndex:[indexPath row]];
[cell.textLabel setText:element.title]; // <--- error at this line of code
return cell;
}
reader.subscritions is NSMutableArray that contains 81 elements ( I'm sure verificated with a NSLog) but when I try to get set the title I have this error... why?
SubscritionArray is a custom class that contains 3 string and 1 int.
Custom class:
header
#import <Foundation/Foundation.h>
#interface SubscriptionArray : NSObject{
NSString *title;
NSString *source;
NSString *htmlUrl;
NSInteger count;
}
#property (nonatomic,retain) NSString *title;
#property (nonatomic,retain) NSString *source;
#property (nonatomic,retain) NSString *htmlUrl;
#property (nonatomic) NSInteger count;
#end
implementation:
#import "SubscriptionArray.h"
#implementation SubscriptionArray
#synthesize title,source,htmlUrl,count;
-(void)dealloc{
[title release];
[source release];
[htmlUrl release];
}
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
title = [[decoder decodeObjectForKey:#"title"] retain];
source = [[decoder decodeObjectForKey:#"source"] retain];
htmlUrl = [[decoder decodeObjectForKey:#"htmlUrl"] retain];
//count = [decoder decodeIntForKey:#"count"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
if (title) [encoder encodeObject:title forKey:#"title"];
if (source) [encoder encodeObject:source forKey:#"source"];
if (htmlUrl) [encoder encodeObject:htmlUrl forKey:#"htmlUrl"];
//if (count) [encoder encodeInteger:count forKey:#"count"];
}
#end
EDIT
Tha's what I get
* -[__NSArrayM objectAtIndex:]: message sent to deallocated instance 0xe5a8260
And that's how I set the NSMutableArray
subscriptions = [[NSMutableArray alloc]init];
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//make a file name to write the data to using the
//documents directory:
NSString *fullFileName = [NSString stringWithFormat:#"%#/subscriptions", documentsDirectory];
[subscriptions removeAllObjects];
subscriptions = [NSKeyedUnarchiver unarchiveObjectWithFile:fullFileName];
I'd venture to say that your array of subscription elements isn't matching up with your row indexes (just a guess). Suggest that you set a debugger breakpoint immediately after you've declared 'element' and before you attempt to set the label. You should be able to see if you have actually obtained one of your SubscriptionArray objets...
The mutable array in reader.subscriptions is getting deallocated before you attempt to reference it in tableView:cellForRowAtIndexPath:. Make sure that the subscriptions array is properly retained or copied by the reader, and that you're not overreleasing it by sending an extra release or autorelease message somewhere. It might help to post the relevant code from the class of the reader instance.

IPhone persist Model

I would like to persist an object of a class (not just NSString´s). For instance, I have this class:
** News.h:**
#import <Foundation/Foundation.h>
#interface News : NSObject
#property (nonatomic, retain) NSString * atrib1;
#property (nonatomic, retain) NSString * atrib2;
#end
** News.m:**
#import "News.h"
#implementation News
#synthesize atrib1;
#synthesize atrib2;
#end
Should I have to use a plist to storage it? How should I do it?
Using NSCoding:
In News.m, I have added:
- (void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:atrib1 forKey:#"key1"];
[encoder encodeObject:atrib2 forKey:#"key2"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
atrib1 = [[decoder decodeObjectForKey:#"key1"] retain];
atrib2 = [[decoder decodeObjectForKey:#"key2"] retain];
return self;
}
-(void)dealloc{
[super dealloc];
[atrib1 release];
[atrib2 release];
}
In News.h:
#interface News : NSObject<NSCoding>{
NSCoder *coder;
}
#property (nonatomic, retain) NSString * atrib1;
#property (nonatomic, retain) NSString * atrib2;
#end
To read update and persist a new object in the plist:
- (IBAction)addANewNews:(id)sender {
//Plist File
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *plistPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"myplist.plist"];
//Reading current news
NSData *oldNews = [NSData dataWithContentsOfFile:plistPath];
NSMutableArray *news = (NSMutableArray *)[NSKeyedUnarchiver unarchiveObjectWithData:oldNews];
if (news == nil)
news = [[NSMutableArray alloc] init];
//Adding a new news
[news addObject:aNewNews];
NSError *error;
NSData* newData = [NSKeyedArchiver archivedDataWithRootObject:news];
//persisting the updated news
BOOL success =[newData writeToFile:plistPath options:NSDataWritingAtomic error:&error];
if (!success) {
NSLog(#"Could not write file.");
}else{
NSLog(#"Success");
}
}