Archiving and Unarchiving objects in Objective-C

Apple, iPhone, Objective-C Add comments

Woohoo … my first Objective-C related post!

Ok, so this is nothing new but I thought I’d log a couple of things that caught me out as a NOOB to the whole iPhone development malarky.

First off archiving your objects is a way of saving them to a property list file that sits on your device and can be unarchived the next time your app loads as a way to persist data locally on the device. Very handy it is too. Now there are better ways to manage data in Obj-C … CoreData for a start. This is a much more powerful way to persist data to a SQL lite database and allows you loads more freedom in what you can actually archive. There are limitations to the property list method you see. You can only archive NS objects and simple scalars so your class structure needs to compensate for this. The ‘plist’ objects you can archive are … NSArray, NSDictionary, NSString, NSNumber, NSDate and NSData along with their mutable subclasses. The scalars are int, float, double etc.

I don’t want to write a massive chapter on this and there’s loads of information online about the technical ins and outs of what’s happening behind the scenes. What I do want to do is share the classes I’ve used in a recent project and explain in basics what’s happening.

Firstly we’ll create the object we want to be archived. Any object that needs to be archived and is a subclass of NSObject must adopt the NSCoding protocol. Take a look at this object …

DataModel Object for archiving

  1. /* INTERFACE */
  2.  
  3. #define kfavesArray @"favesArray"
  4. #define kCurrentFave @"currentFave"
  5. #define kCurrentFaveIndex @"currentFaveIndexPath"
  6.  
  7. #import <Foundation/Foundation.h>
  8.  
  9. @class FaveVO;
  10.  
  11.  
  12. @interface DataModel : NSObject <NSCoding>
  13. {
  14.         NSMutableArray *favesArray;
  15.         FaveVO *currentFave;
  16.         int faveIndex;
  17. }
  18.  
  19. @property (nonatomic, retain) NSMutableArray *favesArray;
  20. @property (nonatomic, retain) FaveVO *currentFave;
  21. @property int faveIndex;
  22.  
  23. -(void)addNewFaveLocation:(FaveVO *)fave;
  24. -(void)createNewFaveVOFromFaveVO:(FaveVO *)fave;
  25. -(void)setFaveLocation:(int)index;
  26. -(void)removeFaveLocation;
  27.  
  28. @end
  29.  
  30. /* IMPLEMENTATION */
  31.  
  32. #import "DataModel.h"
  33. #import "FaveVO.h"
  34.  
  35. @implementation DataModel
  36.  
  37. @synthesize favesArray;
  38. @synthesize currentFave;
  39. @synthesize faveIndex;
  40.  
  41. //------------------------------------------------------
  42. -(id)init
  43. {
  44.         if(self = [super init])
  45.         {
  46.                 favesArray = [[NSMutableArray alloc] init];
  47.         }
  48.        
  49.         return self;
  50. }
  51. //--------------------------------------------------
  52. -(void)addNewFaveLocation:(FaveVO *)fave
  53. {
  54.         if(!favesArray) favesArray = [[NSMutableArray alloc] init];
  55.        
  56.         [favesArray insertObject:fave atIndex:0];
  57.         currentFave = [favesArray objectAtIndex:0];
  58.         faveIndex = 0;
  59.        
  60.         NSLog(@"DataModel currentFave: %@ faveIndex: %i", currentFave,faveIndex);
  61. }
  62. //--------------------------------------------------
  63. -(void)setFaveLocation:(int)index
  64. {
  65.         currentFave = [favesArray objectAtIndex:index];
  66.         faveIndex = index;
  67.        
  68.         NSLog(@"DataModel currentFave: %@ faveIndex: %i", currentFave,faveIndex);
  69. }
  70. //--------------------------------------------------
  71. -(void)removeFaveLocation
  72. {
  73.         currentFave = nil;
  74.        
  75.         NSLog(@"DataModel currentFave: %@ faveIndex: %i", currentFave,faveIndex);
  76. }
  77. //--------------------------------------------------
  78. -(void)createNewFaveVOFromFaveVO:(FaveVO *)fave
  79. {
  80.         double lat = fave.lat;
  81.         double lon = fave.lon;
  82.        
  83.         FaveVO *newFaveVO = [[FaveVO alloc] initWithLabel:[NSString stringWithString:fave.label]
  84.                                                                                 streetAddress:[NSString stringWithString:fave.streetAddress]
  85.                                                                                                  city:[NSString stringWithString:fave.city]
  86.                                                                                                 state:[NSString stringWithString:fave.state]
  87.                                                                                                   zip:[NSString stringWithString:fave.zip]
  88.                                                                                                   lat:lat
  89.                                                                                                   lon:lon];
  90.         [favesArray addObject:newFaveVO];
  91.         [newFaveVO release];
  92. }
  93. //--------------------------------------------------
  94. -(void)dealloc
  95. {
  96.         [favesArray release];
  97.         [currentFave release];
  98.         [super dealloc];
  99. }
  100. //--------------------------------------------------
  101.  
  102.  
  103. //--------------------------------------------------
  104. #pragma mark NSCoding
  105. - (void)encodeWithCoder:(NSCoder *)encoder
  106. {
  107.         [encoder encodeObject:favesArray forKey:kfavesArray];
  108.         [encoder encodeObject:currentFave forKey:kCurrentFave];
  109.         [encoder encodeInt:faveIndex forKey:kCurrentFaveIndex];
  110. }
  111. //--------------------------------------------------
  112. - (id)initWithCoder:(NSCoder *)decoder
  113. {
  114.         NSLog(@"DataModel initWithCoder: %@", decoder);
  115.        
  116.         if (self = [super init])
  117.         {
  118.                 self.favesArray = [decoder decodeObjectForKey:kfavesArray];
  119.                
  120.                 self.faveIndex = [decoder decodeIntForKey:kCurrentFaveIndex];
  121.                 self.currentFave = [favesArray objectAtIndex:faveIndex];
  122.         }
  123.         return self;
  124. }
  125.  
  126. @end

This object also references another object called FaveVO this is an object that I’m feeding into an array and also adopts the NSCoding protocol so you can see that you can archive quite a complicated object overall.

The important stuff is after the line that says “#pragma mark NSCoding”. These methods (using Actionscript terminology but I don’t care) handle what happens when the encoder and decoder go to work on your object.

- (void)encodeWithCoder:(NSCoder *)encoder
{
 [encoder encodeObject:favesArray forKey:kfavesArray];
 [encoder encodeObject:currentFave forKey:kCurrentFave];
 [encoder encodeInt:faveIndex forKey:kCurrentFaveIndex];
}

The encodeObject message takes each of my class variables and copies them into an NSData object using the keys which I defined in the interface. These are unique identifiers that I can use when I want to decode them later like so :

- (id)initWithCoder:(NSCoder *)decoder
{
 if (self = [super init])
 {
  self.favesArray = [decoder decodeObjectForKey:kfavesArray];
  self.faveIndex = [decoder decodeIntForKey:kCurrentFaveIndex];
  self.currentFave = [favesArray objectAtIndex:faveIndex];
 }
return self;
}

And that’s all you need to know about the Object you want to archive. Next comes the class that I’m using to do the archiving and unarchiving.

Here’s the class :

Archiving and Unarchiving DataModel Object

  1. /* INTERFACE */
  2.  
  3. #import <Foundation/Foundation.h>
  4.  
  5. #define kFilename       @"dataarchive.plist"
  6. #define kDataKey                @"Data"
  7.  
  8. @class DataModel;
  9. @interface DataManager : NSObject {
  10.  
  11.         DataModel *dataModel;
  12. }
  13.  
  14. @property (nonatomic, retain) DataModel *dataModel;
  15.  
  16. -(NSString *)dataFilePath;
  17. -(void)initDataModel;
  18. -(void)applicationWillTerminate:(NSNotification *)notification;
  19.  
  20. +(DataManager *)sharedDataManager;
  21.  
  22. @end
  23.  
  24. /* IMPLEMENTATION */
  25.  
  26. #import "DataManager.h"
  27. #import "SynthesizeSingleton.h"
  28. #import "DataModel.h"
  29. #import "FaveVO.h"
  30.  
  31. @implementation DataManager
  32.  
  33. SYNTHESIZE_SINGLETON_FOR_CLASS(DataManager);
  34.  
  35. @synthesize dataModel;
  36.  
  37. //-----------------------------------------------------------------------------------------------------
  38. - (NSString *)dataFilePath
  39. {
  40.         NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  41.         NSString *documentsDirectory = [paths objectAtIndex:0];
  42.         return [documentsDirectory stringByAppendingPathComponent:kFilename];
  43. }
  44. //-----------------------------------------------------------------------------------------------------
  45. -(void)initDataModel
  46. {
  47.         NSString *filePath = [self dataFilePath];
  48.        
  49.         if([[NSFileManager defaultManager] fileExistsAtPath:filePath])
  50.         {
  51.                 NSLog(@"FILE EXISTS");
  52.                
  53.                 NSData *data = [[NSMutableData alloc] initWithContentsOfFile:[self dataFilePath]];
  54.                 NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
  55.                
  56.                 DataModel *decodedDataModel = [unarchiver decodeObjectForKey:kDataKey];
  57.                
  58.                 dataModel = [[DataModel alloc] init];
  59.                
  60.                 NSEnumerator *enumerator;
  61.                 enumerator = [decodedDataModel.favesArray objectEnumerator];
  62.                
  63.                 FaveVO *decodedFaveVO;
  64.                
  65.                 while (decodedFaveVO = [enumerator nextObject])
  66.                 {
  67.                         [dataModel createNewFaveVOFromFaveVO:decodedFaveVO];
  68.                 }
  69.                
  70.                 int index = decodedDataModel.faveIndex;
  71.                 dataModel.faveIndex = index;
  72.                 dataModel.currentFave = [dataModel.favesArray objectAtIndex:index];
  73.                
  74.                 [unarchiver finishDecoding];
  75.                 [unarchiver release];
  76.                 [data release];
  77.         }
  78.         else
  79.         {
  80.                 NSLog(@"FILE DOES NOT EXIST");
  81.                
  82.                 dataModel = [[DataModel alloc] init];
  83.         }
  84.        
  85.         UIApplication *app = [UIApplication sharedApplication];
  86.         [[NSNotificationCenter defaultCenter] addObserver:self
  87.                                                                                          selector:@selector(applicationWillTerminate:)
  88.                                                                                                  name:UIApplicationWillTerminateNotification
  89.                                                                                            object:app];
  90.         
  91. }
  92. //-------------------------------------------------------------
  93. - (void)applicationWillTerminate:(NSNotification *)notification
  94. {
  95.         NSLog(@"Application will terminate");
  96.        
  97.         NSMutableData *data = [[NSMutableData alloc] init];
  98.         NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
  99.        
  100.         [archiver encodeObject:dataModel forKey:kDataKey];
  101.         [archiver finishEncoding];
  102.         [data writeToFile:[self dataFilePath] atomically:YES];
  103.        
  104.         [archiver release];
  105.         [data release];
  106. }
  107.  
  108.  
  109. //--------------------------------------------------
  110. -(void)dealloc
  111. {
  112.         [dataModel release];
  113.         [super dealloc];
  114. }
  115.  
  116.  
  117. @end

The initDataModel method looks for the property list and then uses the NSKeyedUnarchiver object to decode the objects. When the application terminates the objects are encoded using the NSKeyedArchiver object. The class listens for the ApplicationWillTerminate notification using the NSNotificationCenter object. This is similar to the Actionscript event model that uses listeners. The NSEnumerator object is used to step through an array of objects. The decoded FaveVO objects are then sent through to my DataModel class and passed to the createNewFaveVOFromFaveVO method which is where the objects are re-created as shiny new objects. Remember the NSKeyedUnarchiver object is released so you need to retain the objects you need … usually better to create new ones within the correct scope.

Digg!

Leave a Reply

 
WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Log in