How can I save an Objective-C object that's not a property list object or is there a better way for this than a property list?
Asked Answered
R

2

6

I'm writing a Cookbook application, and I've not been able to find anything on how to save the data of a class I've created (the Recipe class). The only way I've seen would be to possibly save the contents of this class as a whole without individually saving every element of the class for each object by making this method for my Recipe class:

-(void) writeToFile:(NSString *)file atomically:(BOOL)atomic{

}

But I have absolutely no idea how I'd go about implementing this to save this object to a file using this method. Some of the properties are:

NSString* name;
UIImage* recipePicture;
NSDate* dateAdded;
NSMutableArray* ingredients; //The contents are all NSStrings.

Does anyone know how to go about saving an object of the Recipe class? It's been driving me crazy not being able to figure it out. Any help would be greatly appreciated. I already have a .plist entitled "RecipeData.plist". Would I just need to write every property to the plist and initialize a new object of recipe with those properties at run time?

Reifel answered 10/11, 2012 at 23:16 Comment(0)
C
5

Adopt:

@interface Recipe : NSObject<NSCoding>

Implement:

- (void)encodeWithCoder:(NSCoder *)coder
{
    [coder encodeObject:name_ forKey:@"name"];
    [coder encodeObject:recipePicture_ forKey:@"recipePicture"];
    [coder encodeObject:dateAdded_ forKey:@"dateAdded"];
    [coder encodeObject:ingredients_ forKey:@"ingredients"];

}

// Decode an object from an archive
- (id)initWithCoder:(NSCoder *)coder
{
    self = [super init];
    if (self!=NULL)
    {
       name_ = [coder decodeObjectForKey:@"name"];
       recipePicture_ = [coder decodeObjectForKey:@"recipePicture"];
       dateAdded_ = [coder decodeObjectForKey:@"dateAdded"];
       ingredients_ = [coder decodeObjectForKey:@"ingredients"];   
    }
    return self;
}

Now in your save:

- (void) save:(NSString*)path recipe:(Recipe*)recipe
{
    NSMutableData* data=[[NSMutableData alloc] init];
    if (data)
    {
        NSKeyedArchiver* archiver=[[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
        if (archiver)
        {
            [archiver encodeInt:1 forKey:@"Version"];
            [archiver encodeObject:recipe forKey:@"Recipe"];
            [archiver finishEncoding];

            [data writeToFile:path atomically:YES];

        }
     }
 }

And in the load:

- (Recipe*) load:(NSString*)path
{
    Recipe* ret=NULL;
    NSData* data=[NSData dataWithContentsOfFile:path];
    if (data)
    {
        NSKeyedUnarchiver* unarchiver=[[NSKeyedUnarchiver alloc] initForReadingWithData:data];
        if (unarchiver)
        {
            int version=[unarchiver decodeIntForKey:@"Version"];
            if (version==1)
            {
                 ret=(Recipe*)[unarchiver decodeObjectForKey:@"Recipe"];
            }
            [unarchiver finishDecoding];
        }
    }
    return ret;
}
Casque answered 10/11, 2012 at 23:21 Comment(3)
You should update this with the other two-thirds of the answer - how to encode it to an NSData object for storage in a property list, and how to decode the data from the property list.Scholem
As I said in the comment below, thank you so much for taking the time to show me how to do this. It's really been a big help.Reifel
You should call self = [super initWithCoder:coder]; instead of self = [super init];. Otherwise, your superclass won't have its encoded data.Spithead
S
2

One option (besides encoding/decoding) is to store each attribute of your class in a dictionary. Then you write the dictionary to the file. The trick is to ensure that every object you put in the dictionary is allowed in a plist. Of the four properties you show, all but the UIImage can be stored as-is.

-(BOOL)writeToFile:(NSString *)file atomically:(BOOL)atomic{
    NSMutableDictionary *data = [NSMutableDictionary dictionary];

    [data setObject:name forKey:@"name"];
    [data setObject:dateAdded forKey@"dataAdded"];

    NSDate *imageData = UIImagePNGRepresentation(recipePicture);
    [data setObject:imageData forKey:@"recipePicture"];

    // add the rest

    return [data writeToFile:file atomically:YES];
}

I updated this to return a BOOL. If it fails, it means one of two things:

  1. The file was inappropriate
  2. You tried to save a non-plist friendly object in the dictonary

You need to add code to avoid trying to add nil objects if you have any. The important thing is to ensure that all keys are strings and only plist-friendly objects are stored (NSString, NSNumber, NSDate, NSValue, NSData, NSArray, and NSDictionary).

Scholem answered 10/11, 2012 at 23:36 Comment(1)
Thank you so much for all the help. It's greatly appreciated- this problem's been bugging me for a while now.Reifel

© 2022 - 2024 — McMap. All rights reserved.