NSCoding Tutorial for iOS: How to Permanently Save App Data

In this NSCoding tutorial, you’ll learn how to save and persist iOS app data so that your app can resume its state after quitting. By Ehab Amer.

Leave a rating/review
Download materials
Save for later
Share
Update note: Ehab Amer updated this tutorial for Swift 4.2, iOS 12 and Xcode 10. Ray Wenderlich wrote the original.

There are many ways to save your data to disk in iOS – raw file APIs, Property List Serialization, Core Data, third-party-solutions like Realm and, of course, NSCoding.

Files and folders are fun to work with!

For apps with heavy data requirements, Core Data or Realm is often the best way to go. For light data requirements, NSCoding is often better because it’s easier to adopt and use.

Swift 4 also offers another lightweight alternative: Codable. This is very similar to NSCoding, but there are some differences.

If you need a simple way to persist data to disk, both NSCoding and Codable are great options! However, neither supports querying or creating complex object graphs. If you need these, you should use Core Data, Realm or another database solution instead.

The main downside to NSCoding is it requires you to depend on Foundation. Codable doesn’t depend on any frameworks, but you must write your models in Swift to use it.

Codable also offers several rich features that NSCoding doesn’t. For example, you can use it to easily serialize models into JSON.

Many Foundation and UIKit class uses NSCoding because it has been around since iOS 2.0 — yes, a decade! Codable was added in Swift 4, just about a year ago, and since many Apple classes are written in Objective-C, they likely won’t be updated anytime soon to support Codable.

Whether you choose to use NSCoding or Codable in your app, it’s a good idea to understand how both work. In this tutorial, you’ll learn all about NSCoding!

Getting Started

You’ll work on a sample project called “Scary Creatures.” This app lets you save photos of creatures and rate how scary they are. However, it doesn’t persist data at this point, so if you restart the app, all of the creatures you added would be lost. Consequently, the app isn’t very useful… yet!

ScaryCreatures app: The scariest of them all!

In this tutorial, you’ll save and load the data of each creature using NSCoding and FileManager. After that, you’ll get acquainted with NSSecureCoding and what it can do to improve data loading in your apps.

To start, click Download Materials at the top or bottom of this tutorial. Open the starter project in Xcode and explore the files in the project:

The main files you’ll work on are:

  • ScaryCreatureData.swift contains the simple data on the creature, the name and rating.
  • ScaryCreatureDoc.swift contains the complete information on the creature including the data, the thumbnail image and the full image of the creature.
  • MasterViewController.swift displays the list of all stored creatures.
  • DetailViewController.swift shows the details of a selected creature and lets you rate it.

Build and run to get a feel for how the app works.

Implementing NSCoding

NSCoding is a protocol that you can implement on your data classes to support the encoding and decoding of your data into a data buffer, which can then persist on disk.

Implementing NSCoding is actually ridiculously easy — that’s why you may find it helpful to use. Watch how quickly you can bang this out!

First, open ScaryCreatureData.swift and add NSCoding to the class declaration like this:

class ScaryCreatureData: NSObject, NSCoding

Then add these two methods to the class:

func encode(with aCoder: NSCoder) {
  //add code here
}

required convenience init?(coder aDecoder: NSCoder) {
  //add code here
  self.init(title: "", rating: 0)
}

You’re required to implement these two methods to make a class conform to NSCoding. The first method encodes an object. The second one decodes the data to instantiate a new object.

In short, encode(with:) is the encoder and init(coder:) is the decoder.

Note: The presence of the keyword convenience here is not a requirement of NSCoding. It’s there because you call a designated initializer, init(title:rating:), within that initializer. If you try to remove it, Xcode will give you an error. If you choose to automatically fix it, the editor will add the same keyword again.

Before you implement these two methods, add the following enumeration in the beginning of the class for the sake of code organization:

enum Keys: String {
  case title = "Title"
  case rating = "Rating"
}

Despite seeming very trivial, it’s worth it. Codable keys use the String data type. Strings are easy to misspell without the compiler catching your mistake. By using an enumeration, the compiler will ensure that you’re always using consistent key names, and Xcode will offer you code completion as you type.

Now, you are ready for the fun part. Add the following to encode(with:):

aCoder.encode(title, forKey: Keys.title.rawValue)
aCoder.encode(rating, forKey: Keys.rating.rawValue)

encode(_:forKey:) writes the value supplied as the first parameter and binds it to a key. The value provided must be of a type that also conforms to the NSCoding protocol.

Add the following code to the beginning of init?(coder:):

let title = aDecoder.decodeObject(forKey: Keys.title.rawValue) as! String
let rating = aDecoder.decodeFloat(forKey: Keys.rating.rawValue)

This does exactly the opposite. You read the value from the NSCoder object provided from a specified key. Since you are saving two values, you want to read the same two values again to resume the app normally.

You now need to actually construct the creature’s data with what you decoded. Replace this line:

self.init(title: "", rating: 0)

with this:

self.init(title: title, rating: rating)

That’s it! With those few lines of code, the class ScaryCreatureData conforms to NSCoding.

Loading and Saving to Disk

You next need to add code to access the disk, read, and write the stored creature data.

For performance efficiency — which is nice to always keep in mind when building an app — you won’t load all the data at once.

Adding the Initializer

Open ScaryCreatureDoc.swift and add the following to the end of the class:

var docPath: URL?
  
init(docPath: URL) {
  super.init()
  self.docPath = docPath    
}

docPath will store where the ScaryCreatureData information is located on disk. The trick here is you should load the information in memory the first time you access it, not with the initialization of the object.

If you are creating a brand new creature, however, this path will be nil because a file wouldn’t be created yet for the document. You’ll add bookkeeping code next to ensure this is set whenever a new creature is created.