[iOS][Swift] Storing Custom Objects into UserDefaults or local persistent storage

Welcome to another Swift / iOS post. Today we are going to see a Swifty way of converting custom Swift objects into Data and storing them into NSUserDefaults for later retrieval.

A few years ago I was working on a project that involved storing such objects into local persistent storage. It was a painful process that involved implementing two methods

  1. required init?(coder: NSCoder)
  2. func encode(with coder: NSCoder)

However, with the introduction of Codable in Swift 4, developers were provided access to APIs that made it easy to convert custom objects into Data and store the Data objects into persistent storage - Whether UserDefaults or Core Data.

Let's look at the example,

Suppose we have a struct named Employee which we want to store into user defaults.


struct Employee {
    let name: String
    let ssn: String
}

Encoding an Object

  1. Conform Employee to Codable protocol. This will allow us to easily encode and decode it back into a custom object
  2. Use the JSONEncoder API to convert Employee object into Data object
  3. Use UserDefaults API to store Data object into persistent storage marked by a specific key

struct Employee: Codable {
    let name: String
    let ssn: String
}

private let employeeDataKey = "employee"

....
..
do {
    let employeeData = try JSONEncoder().encode(Employee(name: "abc def", ssn: "123456789"))
    UserDefaults.standard.set(employeeData, forKey: employeeDataKey)
} catch {
    print(error.localizedDescription)
}
Please note that call to JSONEncoder's encode API can throw an error. We are wrapping it up in do-catch and printing an error for debugging in case code results into an error

Decoding an Object

  1. Since Employee struct already confirms to Codable, it automatically conforms to Decodable protocol as per Apple's documentation. ( typealias Codable = Decodable & Encodable )
  2. Use employeeDataKey key to retrieve Data object from UserDefaults storage
  3. Use JSONDecoder API to decode extracted data object into Employee object
  4. Wrap the call to JSONDecoder API into do-catch block since decoding may throw an error if API cannot successfully decode Data into an instance of passed struct

if let employeeData = UserDefaults.standard.data(forKey: employeeDataKey) {
    do {
        let employeeObject = try JSONDecoder().decode(Employee.self, from: employeeData)

        print(employeeObject.name)
        print(employeeObject.ssn)

    } catch {
        print(error.localizedDescription)
    }
}

Prints,

abc def
123456789

Source code:

The code associated with this post has been compiled and available in this Github gist. It's written in Xcode 13.0 using Swift 5.0 and ready to directly build and compile

Summary

I hope this post gives you a brief overview of how to encode and decode custom objects into and from Data for easier persistent storage - whether it's for Core data or UserDefaults. In the past, I had to use NSKeyedArchiver to achieve the same goal which was difficult to understand and even more challenging to code. But with the introduction of Codable, things have become much easier.

As with any API, I recommend running performance tests to make sure porting your code over to Codable does not lead to regression. Sure, Codable is more readable and concise, but you have to make your own decision regarding which trade-offs you are willing to make for added benefits.

Thanks for reading. As usual, if you have any feedback or suggestion regarding this post, please reach out on Twitter @jayeshkawli.