programming4us
programming4us
MOBILE

iOS SDK : Property Lists and Archiving - Archiving

- How To Install Windows Server 2012 On VirtualBox
- How To Bypass Torrent Connection Blocking By Your ISP
- How To Install Actual Facebook App On Kindle Fire
You can only serialize and deserialize property list objects. Moreover, all of a property list object’s constituent objects must also be property list objects. This limitation hinders the usefulness of property lists. Therefore, rather than using a property list, you can use archiving. Archiving is a more flexible approach to persisting an object than a property list.

You create an archive using an NSKeyedArchiver. This class persists any object that adopts the NSCoding protocol. You reconstitute an object by using NSKeyedArchiver’s complement, the NSKeyedUnarchiver class. In this section, you learn how to create a class that adopts the NSCoding protocol. You then learn how to archive and unarchive this class.

1. Protocols to Adopt

Archiving a class requires that a class adopt the NSCoding protocol. The class should also adopt the NSCopying protocol if you’re creating a class that adopts the NSCoding protocol.

NSCoding

Classes that adopt this protocol must implement the encodeWithCoder: and initWithCoder: methods. The encodeWithCoder: method encodes the object and the object’s instance variables so that they can be archived.

-(void)encodeWithCoder:(NSCoder *)encoder

The initWithCoder: method decodes the object and the object’s instance variables.

-(id)initWithCoder:(NSCoder *)decoder

You use both methods in the example task that follows.

NSCopying

When implementing the NSCoding protocol, best practices dictate that you also implement the NSCopying protocol. Classes that implement the NSCopying protocol must implement the copyWithZone method. Remember, when you set one object to another, you are merely creating another reference to the same underlying physical object. For instance, in the following code, both A and B are pointing to the same Foo that was originally allocated and initialized by A.

Foo * A = [[Foo alloc] init];
Foo * B = A;

When you copy an object, you obtain a distinct physical object, as if the object obtaining the copy actually allocated and initialized the object.

Foo * A = [[Foo alloc] init];
Foo * B = [A copy];

The method that allows copying is the copyWithZone: method.

-(id)copyWithZone:(NSZone *)zone

You can use either this method or NSObject’s copy method to obtain what is called a “deep copy” of an object. For more information, refer to Apple’s “Memory Management Programming Guide for Cocoa,” available online.

Note


2. NSKeyedArchiver and NSKeyedUnarchiver

The NSKeyedArchiver class archives objects, while the NSKeyedUnarchiver class unarchives objects.

NSKeyedArchiver

NSKeyedArchiver stores one or more objects to an archive using the initForWritingWith MutableData method. To be archived, an object must implement the NSCoding protocol.

-(id)initForWritingWithMutableData:(NSMutableData *)data

This method takes a writable data object and returns the archived object as an id. You can then write the archive to disk.

The steps for creating and writing an archive to disk are as follows. First, create an NSMutableData object.

NSMutableData * theData = [NSMutableData data];

After creating the data object, create an NSKeyedArchiver, passing the newly created data object as a parameter.

NSKeyedArchiver * archiver = [[NSKeyedArchiver alloc]
       initForWritingWithMutableData:theData];

After initializing the NSKeyedArchiver, encode the objects to archive. If you wish, you can encode multiple objects using the same archiver, provided all archived objects adopt the NSCoding protocol. The following code snippet illustrates:

[archiver encodeObject:objectA forKey:@"a"];
[archiver encodeObject:objectB forKey:@"b"];
[archiver encodeObject:objectC forKey:@"c"];
[archiver finishEncoding];

After archiving, write the data object, which now contains the archived objects, to a file.

[theData writeToFile:"myfile.archive" atomically:YES]

NSKeyedUnarchiver

You use NSKeyedUnarchiver to unarchive an archive. NSKeyedUnarchiver reconstitutes one or more objects from a data object that was initialized with an archive. To be unarchived, an object must implement the NSCoding protocol. When programming for iOS, you use the initForReadingWithData: method.

-(id)initForReadingWithData:(NSData *)data

The steps to unarchive are similar to archiving. First, create an NSData object from the previously archived file.

NSData * theData =[NSData dataWithContentsOfFile:"myfile.archive"];

After creating the data object, create and initialize an NSKeyedUnarchiver instance.

NSKeyedUnarchiver * uarchiver = [[NSKeyedUnarchiver alloc] initForRead
ingWithData:theData];

After initializing the NSKeyedUnarchiver, unarchive the objects previously archived.

A * objA = [[unarchiver decodeObjectForKey:@"a"] retain];
B * objB = [[unarchiver decodeObjectForKey:@"b"] retain];
C * objC = [[unarchiver decodeObjectForKey:@"c"] retain];
[unarchiver finishDecoding];
[unarchiver release];

Try This: Archiving and Unarchiving an Object

  1. Create a new View-based Application called Encoding.

  2. Create a new Objective-C class called Foo.

  3. Add two properties to Foo. Make one property an NSString and name it “name” and make the other property an NSNumber and name it “age.”

  4. Have Foo adopt the NSCopying and NSCoding protocols (Listings 1 and 2). Remember, Foo must get deep copies of name and age.

  5. Modify Foo so that it implements the encodeWithCoder:, initWithCoder:, and copyWithZone: methods.

  6. Add Foo as a property to EncodingAppDelegate (Listings 3 and 4).

  7. Implement the applicationWillTerminate: method and modify the applicationDidFinish LaunchingWithOptions: method to decode and encode Foo.

  8. If you’re building for SDK 4.0 or later, then iOS will suspend rather than terminate your application, so the applicationWillTerminate will never be called. Edit Encoding-Info.plist in Resources and add another value to the end of the plist with key UIApplicationExitsOnSuspend, type Boolean and value YES. (Later we’ll talk about using archiving to save your application’s state on suspension, so that it can resume where it left off whether iOS terminates it or only suspends it.)

  9. Click Run and the debugging log will indicate that it’s the first pass through. Stop execution and then Run again and the debugging log will indicate that you’ve unarchived the Foo object.


Listing 1. Foo.h
#import <Foundation/Foundation.h>
@interface Foo : NSObject <NSCoding, NSCopying> {
 NSString * name;
 NSNumber * age;
}
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * age;
@end

Listing 2. Foo.m
#import "Foo.h"
@implementation Foo
@synthesize name;
@synthesize age;
-(id) copyWithZone: (NSZone *) zone {
  Foo * aFoo = [[Foo allocWithZone:zone] init];
  aFoo.name = [NSString stringWithString: self.name];
  aFoo.age = [NSNumber numberWithInt:[self.age intValue]];
  return aFoo;
}
-(void) encodeWithCoder: (NSCoder *) coder {
  [coder encodeObject: name forKey: @"name"];
  [coder encodeObject:age forKey: @"age"];
}
-(id) initWithCoder: (NSCoder *) coder {
  self = [super init];
  name = [[coder decodeObjectForKey:@"name"] retain];
  age = [[coder decodeObjectForKey:@"age"] retain];
  return self;
}
-(void) dealloc {
  [name release];
  [age release];
  [super dealloc];
}


					  

Listing 3. EncodingAppDelegate.h
#import <UIKit/UIKit.h>
@class Foo;
@class EncodingViewController;
@interface EncodingAppDelegate : NSObject <UIApplicationDelegate> {
  UIWindow *window;
  EncodingViewController *viewController;
  Foo * myFoo;
}
@property (nonatomic, retain) Foo * myFoo;
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet EncodingViewController
*viewController;
@end

Listing 4. EncodingAppDelegate.m
#import "EncodingAppDelegate.h"
#import "EncodingViewController.h"
#import "Foo.h"
@implementation EncodingAppDelegate
@synthesize window;
@synthesize viewController;
@synthesize myFoo;
- (BOOL)application:(UIApplication *)application
         didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSString *pathToFile = [[NSSearchPathForDirectoriesInDomains(
         NSDocumentDirectory, NSUserDomainMask,YES) objectAtIndex:0]
         stringByAppendingPathComponent:@"foo.archive"];
    NSLog(@"%@",pathToFile);
    NSData * theData =[NSData dataWithContentsOfFile:pathToFile];
     if([theData length] > 0) {
        NSKeyedUnarchiver * archiver = [[[NSKeyedUnarchiver alloc]
             initForReadingWithData:theData] autorelease];
        myFoo = [archiver decodeObjectForKey:@"myfoo"];
         [archiver finishDecoding];
         NSLog(@"nth run - name: %@ age: %i", myFoo.name,
            [myFoo.age intValue]);
     }
    else {
         NSLog(@"first run: no name or age");
         myFoo =[[Foo alloc] init];
         myFoo.name = @"James";
        myFoo.age = [NSNumber numberWithInt:40];
     }
     [window addSubview:viewController.view];
    [window makeKeyAndVisible];
    return YES;
}
-(void) applicationWillTerminate: (UIApplication *) application {
  NSString *pathToFile = [[NSSearchPathForDirectoriesInDomains(
      NSDocumentDirectory, NSUserDomainMask,YES) objectAtIndex:0]
      stringByAppendingPathComponent:@"foo.archive"];
  NSMutableData * theData = [NSMutableData data];
  NSKeyedArchiver * archiver = [[[NSKeyedArchiver alloc]
      initForWritingWithMutableData:theData] autorelease];
  [archiver encodeObject:self.myFoo forKey:@"myfoo"];
  [archiver finishEncoding];
  if([theData writeToFile:pathToFile atomically:YES] == NO)
    NSLog(@"writing failed.... ");
}
-(void)dealloc {
     [myFoo release];
    [viewController release];
    [foo release];
    [window release];
     [super dealloc];
}
 @end


					  

Try This: Archiving and Unarchiving an Object Hierarchy

  1. Open the previous application, Encoding, in Xcode.

  2. Create a new Objective-C class and name it Bar. Have it adopt the NSCoding and NSCopying protocols (Listings 5 and 6).

  3. Add an NSMutableArray as a property in Bar.

  4. Override init to add a couple of Foo objects to the array (Listing 7).

  5. Implement the initWithCoder:, encodeWithCoder:, and copyWithZone: methods (Listing 8).

  6. Add Bar as a property to EncodingAppDelegate. Remember, you must have a forward reference to the class, since you are adding it as a property to the header, and then import the Bar class and synthesize the property in the implementation.

  7. Modify EncodingAppDelegate’s applicationDidFinishLaunchingWithOptions: and applicationWillTerminate: methods to include the newly created Bar property.

  8. Note that we changed the name of the archive file to foo2.archive to avoid conflicting with the previous task.

  9. Click Run.


Listing 5. Bar.h
#import <Foundation/Foundation.h>
#import "Foo.h"
@interface Bar : NSObject <NSCoding, NSCopying> {
 NSMutableArray * foos;
}
@property (nonatomic, retain) NSMutableArray * foos;
@end

Listing 6. Bar.m
#import "Bar.h"
@implementation Bar
@synthesize foos;
-(id) init {
  if([super init] == nil)
    return nil;
  Foo * foo1 = [[Foo alloc] init];
  foo1.name = @"Juliana";
  foo1.age = [NSNumber numberWithInt:7];
  Foo * foo2 = [[Foo alloc] init];
  foo2.name = @"Nicolas";
  foo2.age = [NSNumber numberWithInt:3];
  foos = [[NSMutableArray alloc] initWithObjects:foo1, foo2, nil];
  return self;
}
-(void) encodeWithCoder: (NSCoder *) coder {
 [coder encodeObject: foos forKey:@"foos"];
}
-(id) initWithCoder: (NSCoder *) coder {
  self = [super init];
  foos = [[coder decodeObjectForKey:@"foos"] retain];
  return self;
}
-(id) copyWithZone: (NSZone *) zone {
  Bar * aBar = [[Bar allocWithZone:zone] init];
  NSMutableArray *newArray = [[[NSMutableArray alloc] initWithArray:
      self.foos copyItems:YES] autorelease];
  aBar.foos = newArray;
  return aBar;
}
- (void) dealloc {
  [foos release];
  [super dealloc];
}
@end


					  

Listing 7. EncodingAppDelegate.h
#import <UIKit/UIKit.h>
@class Bar
@class Foo;
@class EncodingViewController;
@interface EncodingAppDelegate : NSObject <UIApplicationDelegate> {
  UIWindow *window;
  EncodingViewController *viewController;
  Foo * myFoo;
  Bar * myBar;
}

@property (nonatomic, retain) Foo * myFoo;
@property (nonatomic, retain) Bar * myBar;
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet EncodingViewController
*viewController;
@end

Listing 8. EncodingAppDelegate.m
#import "EncodingAppDelegate.h"
#import "EncodingViewController.h"
#import "Foo.h"
#import "Bar.h"
@implementation EncodingAppDelegate
@synthesize window;
@synthesize viewController;
@synthesize myFoo;
@synthesize myBar;
-(void)applicationDidFinishLaunching:(UIApplication *)application {
  NSString *pathToFile = [[NSSearchPathForDirectoriesInDomains(
      NSDocumentDirectory, NSUserDomainMask,YES) objectAtIndex:0]
      stringByAppendingPathComponent:@"foo2.archive"];
  NSLog(@"%@", pathToFile);
  NSData * theData =[NSData dataWithContentsOfFile:pathToFile];
  if([theData length] > 0) {
    NSKeyedUnarchiver * archiver = [[[NSKeyedUnarchiver alloc]
         initForReadingWithData:theData] autorelease];
     myFoo = [archiver decodeObjectForKey:@"myfoo"];
     myBar = [archiver decodeObjectForKey:@"mybar"];
     [archiver finishDecoding];
     NSLog(@"nth run - name: %@ age: %i", myFoo.name,
       [myFoo.age intValue]);
    NSArray * array = myBar.foos;
     for(Foo * aFoo in array) {
      NSLog(@"Foo: name: %@, age: %i", aFoo.name, [aFoo.age intValue]);
    }
  }
  else {
    NSLog(@"first run: no name or age");
     myFoo =[[Foo alloc] init];
    myFoo.name = @"James";
    myFoo.age = [NSNumber numberWithInt:40];
    myBar = [[Bar alloc] init];
  }
  [window addSubview:viewController.view];
  [window makeKeyAndVisible];
}
-(void) applicationWillTerminate: (UIApplication *) application {
  NSString *pathToFile = [[NSSearchPathForDirectoriesInDomains(
      NSDocumentDirectory, NSUserDomainMask,YES) objectAtIndex:0]
      stringByAppendingPathComponent:@"foo2.archive"];
  NSMutableData * theData = [NSMutableData data];
  NSKeyedArchiver * archiver = [[[NSKeyedArchiver alloc]
  initForWritingWithMutableData:theData] autorelease];
  [archiver encodeObject:myFoo forKey:@"myfoo"];
  [archiver encodeObject:myBar forKey:@"mybar"];
  [archiver finishEncoding];
  if( [theData writeToFile:pathToFile atomically:YES] == NO)
    NSLog(@"writing failed....");
}
-(void)dealloc {
  [myFoo release];
  [myBar release];
  [viewController release];
  [window release];
  [super dealloc];
}


					  

When the application starts, it loads the archive file into a data object. If the data object is null, the file doesn’t exist. If the file does exist, the data is unarchived. When the application terminates, it archives Foo. Because Bar contains constituent Foo objects in an array, it also archives those objects. The key for the archived Foo is “myfoo,” and “mybar” for the archived Bar object. Both Foo and Bar implement the NSCoding protocol. This allows them to be archived. Notice that Bar contains an NSMutableArray of Foo objects. Because NSMutableArray adopts the NSCoding protocol, NSMutableArray can be encoded and decoded. Moreover, the NSMutableArray knows to encode or decode its constituent elements.

Now examine Bar’s copyWithZone method. Because Bar contains an NSMutableArray of Foo objects, when copying a Bar you must also copy the Bar’s Foo array. But you cannot just set the new Bar’s array to the old Bar’s array, as the new Bar’s array will simply be a pointer to the old Bar’s array. Instead you must create a new NSMutableArray and initialize the new array with the old array, being certain to specify copyItems as YES. By taking this step, the new Bar obtains a deep copy of the old Bar’s array of Foo objects.

Note

For more information on archiving, refer to “Apple’s Archives and Serializations Programming Guide for Cocoa.”


3. Multitasking and Saving Application State

In versions of the iOS prior to 4, when the user pressed the Home button, your application was terminated. Now the default behavior is to leave your application in memory and just suspend it. Then if the user wants to return to it later, it can launch instantly and they’re exactly where they left off. We actually had to turn this behavior off in the encoding task so that it would terminate and save our test data. For simple apps that launch quickly and aren’t likely to be returned to over and over, it makes sense to set that flag and not worry about coding for application suspension. However, for more complex applications your users will really appreciate being able to switch to another app and then instantly return to yours, continuing right where they left off.

So, what does this have to do with archiving? If all the iOS ever did was temporarily freeze your app in memory and then continue it later, there wouldn’t be any need for persistence. But, if iOS runs low on memory or the user doesn’t return to your application for a while, then it can be purged from memory without warning. This creates unpredictable behavior for your users. Sometimes when they switch to another app and then return to yours, everything is exactly as they left it (e.g., they’re in the middle of a gaming level or tunneled deeply down into some nested UITableViews). Other times, when they return to your app it seems to be starting up fresh (because it is—iOS had to purge it from memory). The recommended way to deal with this inconsistency is to always save away enough state information when your application is suspended so that you can restore it to the exact same context if your application is purged from memory before the user can return to it. That’s where archiving comes in.

The state information you’ll need to save is going to be different for every application, but the easiest way to save that information is going to be a set of objects serialized and archived to a file in the application’s Documents directory. For a game you might need to serialize a number indicating the level they were on along with a hierarchy of objects that encode the state of enemies, puzzles, etc., on that level. For a reference application that browses hierarchical data, it might be saving a trail of which item they chose at each level and where they were scrolled to on the current view. In either case, you will likely want to implement the NSCoding protocol for a selection of objects, so that you can archive them when your application is suspended. Implement the applicationDidEnterBackground method in your AppDelegate class and save your state information there as well as freeing up any resources you don’t need.

- (void)applicationDidEnterBackground:(UIApplication *)application

Implement the applicationWillEnterForeground method in your AppDelegate class to restore your state information.

- (void)applicationWillEnterForeground:(UIApplication *)application
Other  
 
Youtube channel
Top 10
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
- Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
- Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
REVIEW
- First look: Apple Watch

- 3 Tips for Maintaining Your Cell Phone Battery (part 1)

- 3 Tips for Maintaining Your Cell Phone Battery (part 2)
programming4us programming4us
programming4us
 
 
programming4us