5

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.

Ali
  • 4,205
  • 4
  • 25
  • 40

3 Answers3

11

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;
}
Mike Weller
  • 45,401
  • 15
  • 131
  • 151
  • Thanks for your help. The idea is clear to me now. One last question: for the step (NSArray *myArray = [NSKeyedUnarchiver unarchiveObjectWithData:serialized];) would give me an NSArray while my original array is an NSMutableArray. So should I use something like [copy mutable]? – Ali Oct 13 '11 at 07:21
  • If it gives you back a standard `NSArray` then use `[array mutableCopy]` to get a mutable version. – Mike Weller Oct 13 '11 at 07:35
4

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];
Jason Harwig
  • 43,743
  • 5
  • 43
  • 44
  • Thanks for you answer. My other question is: I already have 2 methods in the Occasion object called:( - (id)init AND - (id)initWithTitle:(NSString *)title date:(NSDate *)date imagePath:(NSString *)imagePath ) Now should I cancel these 2 methods and use initWithCoder and encodeWithCoder instead whenever I want make new instances of Occasion? – Ali Oct 13 '11 at 07:56
  • No, when you want to create objects use still use the init or initWithTitle:date:imagePath. The coder methods are only for serialization. – Jason Harwig Oct 13 '11 at 14:26
1

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.

Nikolai Ruhe
  • 81,520
  • 17
  • 180
  • 200