Friday, April 4, 2014

NSCoding Protocol

For many applications to have real functionality,  you need a way to persist data. This simple Objective-C example shows you how to do just that by conforming to the NSCoding protocol within a user-defined data type (class). For this example, we will create and run this code as a command line tool, defining the interface and implementation in the same file.

In Xcode, select: File > New > Project.

In the OS X Templates select: Application > Command Line Tool > Next.



Enter the project options.
Let's call it: NSCodingDemo
Type your organization or company name.
Type your company identifier which should follow the reverse domain convention.


We can put our interface, implementation and driver code into the main.m file that is created with the command line tool project template.

First, add the interface and implementation code just after the import and before the 'main' method.

#import <Foundation/Foundation.h>

// 1.)
@interface Car : NSObject <NSCoding>

// 2.)
@property (nonatomic) int year;
@property (nonatomic, strong) NSString *make;
@property (nonatomic, strong) NSString *model;

@end

// 3.)
@implementation Car

- (void)encodeWithCoder:(NSCoder *)aCoder {
    // 4.)
    [aCoder encodeInt:_year forKey:@"year"];
    [aCoder encodeObject:_make forKey:@"make"];
    [aCoder encodeObject:_model forKey:@"model"];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    
    self = [super init];
    if (self) {
        // 5.)
        [self setYear:[aDecoder decodeIntForKey:@"year"]];
        [self setMake:[aDecoder decodeObjectForKey:@"make"]];
        [self setModel:[aDecoder decodeObjectForKey:@"model"]];
    }
    
    return self;
}


@end

1.) The 'Car' class inherits from NSObject and conforms to the NSCoding protocol.
2.) The interface declares three properties - an int for the 'year' and NSString for the 'make' and 'model'.
3.) Since the Car class conform to the NSCoding protocol, we must implement both required methods. 'encodeWithCoder:' and 'initWithCoder:'
4.) Encode the objects for archiving.
5.) Decode the objects when unarchiving.

Next, replace the 'main' method with this code which will create three instances of the 'Car' class and demonstrate Archiving and Unarchiving the data.

int main(int argc, const char * argv[])
{
    
    @autoreleasepool {
        // 6.)
        Car *car1 = [[Car alloc] init];
        Car *car2 = [[Car alloc] init];
        Car *car3 = [[Car alloc] init];
        
        car1.year = 1957;
        car1.make = @"Chevrolet";
        car1.model = @"Bel Air";
        
        car2.year = 1964;
        car2.make = @"Dodge";
        car2.model = @"Polara";
        
        car3.year = 1972;
        car3.make = @"Plymouth";
        car3.model = @"Fury";
        
        // 7.)
        NSArray *documentDirectories =
        NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                            NSUserDomainMask,
                                            YES);
        
        // Get document directory from that list
        NSString *documentDirectory = [documentDirectories objectAtIndex:0];
        
        NSString *path = [documentDirectory   stringByAppendingPathComponent:@"archiver_demo.archive"];
        // 8.)
        NSMutableArray *allCars = [[NSMutableArray alloc] initWithObjects:car1, car2, car3, nil];
        
        // The 'archiveRootObject:toFile' returns a bool indicating
        // whether or not the operation was successful. We can use that to log a message.
        // 9.)
        if ([NSKeyedArchiver archiveRootObject:allCars toFile:path])
            NSLog(@"Success writing to file!");
        else
            NSLog(@"Unable to write to file!");
        
        // 10.)
        NSMutableArray *carsAgain = [[NSMutableArray alloc] init];
        
        // Unarchive the objects and put them in a new array
        carsAgain = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
        
        // Use fast enumeration to output the new array
        for (Car *c in carsAgain)
            NSLog(@"\nHere's a %d %@ %@", c.year, c.make, c.model);
        
    }
    return 0;

}

6.) Create three instances of 'Car' and set the properties of each.
7.) Create a file path for archiving.
8.) Create an array of the three 'Car' instances. This will be the root object that gets archived.
9.) In the NSKeyedArchiver class method 'archiveRootObject:toFile:', returns a BOOL value indicating success or failure of the operation. We'll use this value to log a message to the console.
10.) Now we unarchive the data and put it into a new array to verify that the data is in tact.

If you’re running this as a command line tool, as in this example, your *.archive file should be found in your /Users/username/Documents directory. If you open it with TextEdit.app, you should see a cryptic representation of the string data from the original objects. Typically, an application would create a separate datastore class and call the ‘archiveRootObject:toFile:’ and ‘unarchiveObjectWithFile:’ methods from methods within the datastore class. These datastore methods could then be called from application launching and closing methods.

No comments:

Post a Comment