NSKeyedArchiver and NSKeyedUnarchiver

You now have a place to save data on the filesystem and a model object that can be saved to the filesystem. The final two questions are: How do you kick off the saving and loading processes, and when do you do it? To save instances of Item, you will use the class NSKeyedArchiver when the application exits.

In ItemStore.swift, implement a new method that calls archiveRootObject(_:toFile:) on the NSKeyedArchiver class.

func saveChanges() -> Bool {
    print("Saving items to: (itemArchiveURL.path)")
    return NSKeyedArchiver.archiveRootObject(allItems, toFile: itemArchiveURL.path)
}

The archiveRootObject(_:toFile:) method takes care of saving every single Item in allItems to the itemArchiveURL. Yes, it is that simple. Here is how archiveRootObject(_:toFile:) works:

  • The method begins by creating an instance of NSKeyedArchiver. (NSKeyedArchiver is a concrete subclass of the abstract class NSCoder.)

  • The method encode(with:) is called on allItems and is passed the instance of NSKeyedArchiver as an argument.

  • The allItems array then calls encode(with:) to all of the objects it contains, passing the same NSKeyedArchiver. Thus, all your instances of Item encode their instance variables into the very same NSKeyedArchiver (Figure 16.4).

  • The NSKeyedArchiver writes the data it collected to the path.

Figure 16.4  Archiving the allItems array

Illustration shows the allItems array archive and its various objects.

When the user presses the Home button on the device, the message applicationDidEnterBackground(_:) is sent to the AppDelegate. That is when you want to send saveChanges to the ItemStore.

Open AppDelegate.swift and add a property to the class to store the ItemStore instance. You will need a property to reference the instance in applicationDidEnterBackground(_:).

class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    let itemStore = ItemStore()

Then update application(_:didFinishLaunchingWithOptions:) to use this property instead of the local constant.

func application(_ application: UIApplication, didFinishLaunchingWithOptions
        launchOptions: [UIApplicationLaunchOptionsKey : Any]?) -> Bool {
    // Override point for customization after application launch.

    // Create an ItemStore
    let itemStore = ItemStore()

    // Create an ImageStore
    let imageStore = ImageStore()

    // Access the ItemsViewController and set its item store and image store
    let navController = window!.rootViewController as! UINavigationController
    let itemsController = navController.topViewController as! ItemsViewController
    itemsController.itemStore = itemStore
    itemsController.imageStore = imageStore

    return true
}

Because the property and the local constant were named the same, you only needed to remove the code that created the local constant.

Now, still in AppDelegate.swift, implement applicationDidEnterBackground(_:) to kick off saving the Item instances. (This method may have already been implemented by the template. If so, make sure to add this code to the existing method instead of writing a new one.)

func applicationDidEnterBackground(_ application: UIApplication) {
    let success = itemStore.saveChanges()
    if (success) {
        print("Saved all of the Items")
    } else {
        print("Could not save any of the Items")
    }
}

Build and run the application on the simulator. Create a few instances of Item, then press the Home button to leave the application. Check the console and you should see a log statement indicating that the items were saved.

While you cannot yet load these instances of Item back into the application, you can still verify that something was saved.

In the console’s log statements, find one that logs out the itemArchiveURL location and another that indicates whether saving was successful. If saving was not successful, confirm that your itemArchiveURL is being created correctly. If the items were saved successfully, copy the path that is printed to the console.

Open Finder and press Command-Shift-G. Paste the file path that you copied from the console and press Return. You will be taken to the directory that contains the items.archive file. Press Command-Up to navigate to the parent directory of items.archive. This is the application’s sandbox directory. Here, you can see the Documents, Library, and tmp directories alongside the application itself (Figure 16.5).

Figure 16.5  Homepwner’s sandbox

Screenshot shows the Homepwner application’s sandbox.

The location of the sandbox directory can change between runs of the application; however, the contents of the sandbox will remain unchanged. Due to this, you may need to copy and paste the directory into Finder frequently while working on an application.

Loading files

Now let’s turn to loading these files. To load instances of Item when the application launches, you will use the class NSKeyedUnarchiver when the ItemStore is created.

In ItemStore.swift, override init() to add the following code.

init() {
    if let archivedItems =
        NSKeyedUnarchiver.unarchiveObject(withFile: itemArchiveURL.path) as? [Item] {
        allItems = archivedItems
    }
}

The unarchiveObject(withFile:) method will create an instance of NSKeyedUnarchiver and load the archive located at the itemArchiveURL into that instance. The NSKeyedUnarchiver will then inspect the type of the root object in the archive and create an instance of that type. In this case, the type will be an array of Items because you created this archive with a root object of type [Item]. (If the root object were an instance of Item instead, then unarchiveObject(withFile:) would return an Item.)

The newly created array is then sent init(coder:) and, as you may have guessed, the NSKeyedUnarchiver is passed as the argument. The array starts decoding its contents (instances of Item) from the NSKeyedUnarchiver and sends each of these objects the message init(coder:), passing the same NSKeyedUnarchiver.

Build and run the application. Your items will be available until you explicitly delete them. One thing to note about testing your saving and loading code: If you kill Homepwner from Xcode, the method applicationDidEnterBackground(_:) will not get a chance to be called and the item array will not be saved. You must press the Home button first and then kill it from Xcode by clicking the Stop button.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset