Archiving and UnArchiving Your App Data

If you are a new visitor to our site, please read the Getting Started guide.

In this workshop, you will learn how to use the NSKeyedArchiver and NSKeyedUnarchiver class to save and restore your application’s data in a binary file. You will also learn how to create a binary file in the application’s sandbox.

iOS provides several mechanism for saving and restoring your application’s data in its sandbox. One method available is to use the NSKeyedArchiver and NSKeyedUnarchiver class. These classes do not provide the scalability of a database, but they are easy to use. Also, they can archive/unarchive all kinds of objects, not just strings, arrays, and dictionaries.

The NSKeyedArchiver Class
This class provides the ability to encode data into binary data that is written to a binary file. The binary file must already exist in the application sandbox. The process of converting an object into binary data is called encoding.

By the way, the NSKeyedArchiver class encodes the entire content of the object (an NSArray for example) before writing it to the binary file.

The NSKeyedUnarchiver Class
This class decode a binary file’s data. Once the binary data have been decoded, you can place it your view’s controls; for example, a textView control or a pickerView control. The process of transforming binary data into a usable object is called decoding.

By the way, the NSKeyedUnarchiver class decode the entire content of the binary file. This mean you’d have to use a for or while loop and an if statement to access a specific object and its properties.

Download The Workshop Project

Now you know the purpose of the NSKeyedArchiver and NSKeyedUnarchiver class, I will show you how to use them in an Xcode project I’ve already created for this workshop; so, click the link below to download it.
download-excodeproj
After unzipping the file, use Finder to drag-and-drop the PlistApp folder in the iOS Apps folder you’ve created on your Mac.
plistworkshop-fig01
Use Finder to locate the file, PlistApp.xcodeproj then double-click it to launch it in Xcode.
plistworkshop-fig02

Take The App for a Spine

plistworkshop-fig03In Xcode, click the viewController.xib file to launch it in Interface Builder. Take a look at the view. As you can see, I’ve already placed these controls on it for you:

  • 1 textField control
  • 2 button controls (Save and Delete)
  • 1 textView control that display some descriptive text
  • 1 pickerView control

I’ve added code in the viewController.h file and the viewController.m file. I’ve connected the view’s controls in Interface Builder for you too. So go ahead, run the application, and try out the view’s control, except the pickerView control. The app will crash if you try to use it. Notice how the keyboard is dismissed when you click the view’s background or the return key. That’s because I’ve added code in the viewController.m file to handle these events. When you are ready, terminate the app by simply returning to Xcode, then press the (command + .) keys on your keyboard; or click the toolbar’s Stop button.

What Will The App Do?

The big question is, what will the app do? Well, controls on the viewController.xib file pretty much answer that question. In a nutshell, the app will allow the user to do these tasks:

  1. Add books in a binary file
  2. View a listing of books in a pickerView control
  3. Delete books from the binary file

You’ll have to add code in the ViewController class and the AppDelegate class to implement above user requirements.

Create The Binary File

Before the user can add books in the binary file, you’ll have to add code in the AppDelegate (.h and .m) files to create a binary file called BooksOwned.archive in the Documents directory. As you already know, that directory is located in the Plist App’s sandbox. Here’s a snap shot of what the sandbox currently look like on my computer.
plistworkshop-fig04
As you can see, the Documents directory is empty. Now is a good time to use Finder to access the Plist App sandbox. Take a look at the file path shown in above image. It shows the path to the PlistApp sandbox. Once you’re there, expand the Documents folder, so you can see the binary file created there later on.

Now, I want you to click the AppDelegate.h file add highlighted code.

#import <UIKit/UIKit.h>
@class ViewController;

@interface AppDelegate : UIResponder

/******* PROPERTY DECLARATIONS *****/
@property (strong, nonatomic) UIWindow *window;

@property (nonatomic, retain) NSString *dataFilePath;

@property (nonatomic, retain) NSMutableArray *fileContent;

@property (strong, nonatomic) ViewController *viewController;

/****** METHOD DECLARATIONS ******/

- (NSString *)getPathToDocumentsDir;

- (void)createBinaryFile:(NSString *)fileName;

@end

Next, click the AppDelegate.m file and add highlighted code.

#import "AppDelegate.h"
#import "ViewController.h"

@implementation AppDelegate

/******************* METHOD IMPLEMENTATION ***********************************************************/
- (NSString *)getPathToDocumentsDir
{
  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  NSString *documentsDir = [paths objectAtIndex:0];

  // Return the full path to sandbox's Documents directory
  return documentsDir;
}

- (void)createBinaryFile:(NSString *)fileName
{
  // Create an object for interacting with the sanbox's file system
  NSFileManager *fm = [NSFileManager defaultManager];

  // Initialize the property variable with the full path to the sandbox's Documents directory, which is where the binary file will be created
self.dataFilePath = [self.getPathToDocumentsDir stringByAppendingPathComponent:fileName];

  // Check to see if the binary file exists in the sandbox's Documents directory
  BOOL fileExists = [fm fileExistsAtPath:self.dataFilePath];

  if (fileExists == NO) {
    // The binary file does not exist in the sandbox's Documents directory, so initialize the property array variable
    self.fileContent = [[NSMutableArray alloc] init];

    // This statement creates the binary file in the sandbox's Documents directory and initialize it with the empty array called, bookArray
    [NSKeyedArchiver archiveRootObject:self.fileContent toFile: self.dataFilePath];
    NSLog(@"The binary file, (BooksOwned.archive) was successfully created in the sandbox's Documents folder.");
  }
}
/****************************************************************************************************/

The next thing I want you to do, is add this statement in the AppDelegate.m file’s didFinishLaunchingWithOptions method.

 [self createBinaryFile:@"BooksOwned.archive"];

In a nutshell, you set up two custom methods in the AppDelegate class. The first one, getPathToDocumentsDir, gets the full path to the sandbox’s Documents directory and return it to the caller of the method. The second method, createBinaryFile, creates a binary file in the sandbox’s Documents directory, only if it haven’t been created there yet. The name of the file the method creates is passed as a parameter.

Ok, run the application in Xcode by clicking the Run button on the toolbar button. As Xcode build and run the app in the iPhone Simulator, you should keep your eyes on the Plist App sandbox’s Documents directory, which you should have already expanded in Finder. In seconds, the BooksOwned.archive file is created right before your eye.
plistworkshop-fig05
The arrow in above snap shot shows the Objective-C statement responsible for creating the empty binary file in the sandbox’s Documents directory. The statement is a message, which says this:

AppDelegate (self), create a binary file called, BooksOwned.archive in the PlistApp sandbox’s Documents directory.

The Save Button Code

Ok, the BooksOwned.archive file is created in the sandbox’s Documents directory. We can now turn our attention to adding code in the SaveButtonTapped method. It is connected to the Save button control. When the user tap it, code the SaveButtonTapped is fired. This mean we need to add code in the method to basically save the book title the user enters in the textField control, in the BooksOwned.archive file.
plistworkshop-fig06
In order to figure out what code to add in the saveButtonTapped method, I used this pseudocode to express the method’s algorithm. Pseudocode is basically false code, used by programmers to express the application’s algorithm. An algorithm is like a recipe: it list the steps involved in accomplishing a task. Expressing the saveButtonTapped method’s algorithm in pseudocode, makes it easy to transform the pseudocode into Objective-C code. There is no standard form of pseudocode-you make up your own rules. The only-guideline is that the meaning of the pseudocode should be clear to anyone reading it.

Pseudocode for the SaveButtonTapped Method

Armed with above pseudocode, let us add code in the viewController.m file. First, click the file to load it in the code editor, then add this import statement below existing one. This import statement import the AppDelegate class, so we can use its properties and methods in the viewController class.

#import "ViewController.h"
#import "AppDelegate.h"

Next, scroll down to the saveButtonTapped method and add this code. Notice how I used steps of above pseudocode to document the button’s code.

// This method is fired when the user tap the Save button control
- (IBAction)saveButtonTapped {
  // Declare and initialize a string variable bookTitle
  NSString *bookTitle = self.bookTitle.text;

  // Remove leading and trailing white spaces and blank lines from the textField control
  NSString *trimedBookTitle = [bookTitle stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]];

  if (trimedBookTitle.length > 0) {
    // Create an object of the AppDelegate so we can use its properties and methods
    AppDelegate *delegate = [[UIApplication sharedApplication] delegate];

    // Add the bookTitle in the binary file
    [delegate.self addBookInFile:trimedBookTitle];

    // Reinitialize the pickerView control's dataSource object
    self.pickerViewDataSource = [delegate.self fetchBooksFromFile];

    // Clear out the bookTitle control
    self.bookTitle.text = nil;

    // Set the messageLabel control's text color to green
    self.messageLabel.textColor = [UIColor colorWithRed:0.00 green:0.50 blue:0.00 alpha:1.00];

    // Display a success message in the messageLabel
    self.messageLabel.text = @"Book saved successfully.";

    // Enable the pickerView control because there's at least one row in its component
    self.pickerView.userInteractionEnabled = YES;

    // Refresh the pickerView control so the book title the user entered in the textField control shows up in the pickerView
    [self.pickerView reloadAllComponents];

    // Get the index number of the last row that's in the pickerView control, then place it
    // in the lastRowIndex variable
    int lastRowIndex = self.pickerViewDataSource.count - 1;

    // Highlight the last row of the pickerView control
    [self.pickerView selectRow:lastRowIndex inComponent:0 animated:YES];
  } else {
    // Set the messageLabel's text color to red
    self.messageLabel.textColor = [UIColor colorWithRed:0.79 green:0.16 blue:0.14 alpha:1.00];

    // Clear out the textField control
    self.bookTitle.text = nil;

    // Display an error message in the messageLabel control
    self.messageLabel.text = @"Please enter a Book Title in above field before tapping the Save button.";
  }
}

I want you to add this code in the AppDelegate.h file, which declare methods used on in above code (line 14 and 17 ). While you are there, declare the third method you see below as well.

- (BOOL)addBookInFile:(NSString *)newBook;
- (NSMutableArray *)fetchBooksFromFile;
- (void)deleteBookFromFile:(NSUInteger)rowNo;

Next, add this code in the AppDelegate.m file, which implement above methods.

- (BOOL)addBookInFile:(NSString *)newBook
{
  // Create an object for interacting with the sandbox's file system
  NSFileManager *fm = [NSFileManager defaultManager];

  // Get the full path to the binary file
  self.dataFilePath = [self.getPathToDocumentsDir stringByAppendingPathComponent:@"BooksOwned.archive"];

  // Check to see if the binary file exists in the sandbox's Documents directory
  BOOL fileExists = [fm fileExistsAtPath:self.dataFilePath];

  if (fileExists == YES) {
    // Decode the binary file and place its content in the mutable array
    self.fileContent = [NSKeyedUnarchiver unarchiveObjectWithFile:self.dataFilePath];

    // Add the newBook in the bookArray variable
    [self.fileContent addObject:newBook];

    // Encode the dictionary entries, then save it in the binary file
    [NSKeyedArchiver archiveRootObject:self.fileContent toFile: self.dataFilePath];

    // The book was successfully added in the file, so return YES to the caller of this method
    return YES;
  } else {
    // Unable to to add the book in the file because the file doesn't exist in the Documents dir, so return no to the caller of this method
    return NO;
  }
}

- (NSMutableArray *)fetchBooksFromFile
{
  // Get the full path to the binary file
  self.dataFilePath = [self.getPathToDocumentsDir stringByAppendingPathComponent:@"BooksOwned.archive"];

  // Initialize the array
  self.fileContent = [[NSMutableArray alloc] init];

  // Decode the binary file file's content, place it in the array
  self.fileContent = [NSKeyedUnarchiver unarchiveObjectWithFile:self.dataFilePath];

  // Return the binary file's content to the caller of this method or an empty array
  return self.fileContent;
}

- (void)deleteBookFromFile:(NSUInteger)rowNo;
{
  // Fetch all records from the binary file
  self.fileContent = [[NSMutableArray alloc] init];
  self.fileContent = [self fetchBooksFromFile];

  for (int index=0; index < self.fileContent.count; index++) {
    if (index == rowNo) {
      NSLog(@"Titles match %i",rowNo);
      // Remove the element matching the bookTitle parameter from the mutable array
      [self.fileContent removeObjectAtIndex:rowNo];
    }
  }
  // Encode the dictionary entries, then save it in the binary file
  [NSKeyedArchiver archiveRootObject:self.fileContent toFile: self.dataFilePath];
}

Test The Save Button Code

When you are done, run the application. Test the SaveButtonTapped method’s if() block statement by entering a book title in the textfield control, then click the Save button. You should see output shown in Figure (a) below. Next, test the else block’s code by clicking the Save button without entering text in the textField control. You should see output shown in Figure (b) below.

plistworkshop-fig07
Figure (a)
plistworkshop-fig08
Figure (b)

The pickerView Control Code

You need to add code in the viewController class files to fetch content from the BooksOwnned file, and load the pickerView control with book titles. You’ll have to add code in the class to delete the component row the user selected from the pickerView control and the BooksOwned.archive file as well. In order to fulfill these requirement, you’ll have add this code in the viewDidLoad method, which fetch data from the BooksOwned.archive file and place them in the pickerViewDataSource array variable. This array is known as the pickerView control’s dataSource object. It hold data that’ll be displayed in the pickerView component rows like this:

plistworkshop-fig10

Figure (c)

Here are the pickerView control’s dataSource methods to add in the ViewController.m file, which display data in the pickerView control’s component rows.

#pragma mark - pickerView dataSource methods

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
  return 1;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
  return self.pickerViewDataSource.count;
}

- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
  return [self.pickerViewDataSource objectAtIndex:row];
}

-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent (NSInteger)component
{
  // Set the message Label's text color to green
  self.messageLabel.textColor = [UIColor colorWithRed: 0.09 green:0.63 blue:0.18 alpha:1.00];

  // Display content of the row the user picked, in the third Label control
  NSString *itemPicked = [self.pickerViewDataSource objectAtIndex:row];
  self.messageLabel.text = [@"You picked, " stringByAppendingString:itemPicked];
}

Here is the delegate method of the pickerView control to add in the viewController.m file.

#pragma mark - pickerView delegate method

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
  // Check to see if there are any rows in the pickerView control's component
  if (self.pickerViewDataSource.count == 0) {
    // Disable the pickerView control because there are no rows in its component
    self.pickerView.userInteractionEnabled = NO;
  } else if (self.pickerViewDataSource.count != 0 || self.selectedTitle == nil) {
    // Put the book title the user selected in the selectedTitle property variable
    self.selectedTitle = [self.pickerViewDataSource objectAtIndex:row];

    // Put the component row's index number in an NSUInteger variable
    self.selectedRowNo = row;

    // Set the messageLabel control's text color to green
    self.messageLabel.textColor = [UIColor colorWithRed: 0.09 green:0.63 blue:0.18 alpha:1.00];

    // Display the selected title in the messageLabel
    self.messageLabel.text = [self.pickerViewDataSource objectAtIndex:row];

    // Check again to see if there are any rows in the pickerView control's component
    if(self.pickerViewDataSource.count == 0) {
      // There aren't any, so disable it
      self.pickerView.userInteractionEnabled = NO;
    }
  }
}

The viewDidLoad Method Code

Before testing the pickerView control’s code, you’ll have to add these statements in the viewController.h file’s viewDidLoad method.

- (void)viewDidLoad
{
  [super viewDidLoad];

  // Initialize this property viable
  self.selectedRowNo = 0;

  // Create an object of the AppDelegate so we can use its variables and methods
  AppDelegate *delegate = [[UIApplication sharedApplication] delegate];

  // Initialize the pickerView control's dataSource object
  self.pickerViewDataSource =  [[NSMutableArray alloc] init];

  // Fetch book titles from the binary file and add them in the pickerView control's dataSource
  self.pickerViewDataSource = [delegate.self fetchBooksFromFile];

  // Check to see if there are any rows in the pickerView control's component
  if (self.pickerViewDataSource.count == 0) {
    // Disable the pickerView control because there aren't any rows in its component
    self.pickerView.userInteractionEnabled = NO;
  }
}

Test The PickerView Control Code

You entered code in a delegate method of the pickerView control, which highlight the pickerView component row the user selects and display it in the messageLabel, located below the Save button control.

plistworkshop-fig10

I want you to use the viewController’s textField and Save button to add these books in the BooksOwned.archive file. Notice how the book title appears in the pickerView control and is highlighted, when you click the Save button.

The Fierce Dispute
Simple Program Design
One for The Money
From Dead to Worse
Living Dead in Dallas
Hush
Lasher
Interview with The Vampire
Dead in The Family

Once you’ve entered the last title, terminate the application. When you relaunch it, the book titles you added in the BooksOwned.archive file shows up in the pickerView control-see image above.

The Delete Button Code

The last piece of code you need to add in the viewController.m file will go in the deleteButtonTapped method. It is connected to the Delete button control.

// This method is fired when the user tap the Delete button control
- (IBAction)deleteButtonTapped
{
  // Check to see if there are any rows in the pickerView control's component
  if (self.pickerViewDataSource.count == 0) {
  // Disable the pickerView control because there aren't any rows in its component
  self.pickerView.userInteractionEnabled = NO;
  } else {
    // Delete the book title the user selected from the pickerViewDataSource object
    [self.pickerViewDataSource removeObjectAtIndex:self.selectedRowNo];

    // Check again to see if there are any rows in the pickerView control's component
    if(self.pickerViewDataSource.count == 0) {
      // There aren't any, so disable it
      self.pickerView.userInteractionEnabled = NO;
    }

    // Create an object of the AppDelegate so we can use its variables and methods
    AppDelegate *delegate = [[UIApplication sharedApplication] delegate];

    // Save the pickerViewDataSource object in the binary file
    [NSKeyedArchiver archiveRootObject:self.pickerViewDataSource toFile:[delegate.self dataFilePath]];

    // Refresh the pickerView control so the book title the user deleted doesn't appear any more in the pickerView
    [self.pickerView reloadAllComponents];
    if (self.pickerViewDataSource.count == 0) {
      self.selectedRowNo = 0;
    }
  }
}

Once you’ve entered above code in the Delete button’s method, run the application and delete one or all book titles from the pickerView control, then terminate the app. When you relaunch the app again, rows you deleted from the pickerView control don’t show up. That’s because you’ve added code in the deleteButtonTapped method to delete a book title from the pickerView’s dataSource object (pickerViewDataSource) and the binary file (BooksOwned.archive).

This concludes this week’s workshop on how to use the NSKeyedArchiver Class and the NSKeyedUnarchiver class.