[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
required init?(coder: NSCoder)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
- Conform
EmployeetoCodableprotocol. This will allow us to easily encode and decode it back into a custom object - Use the
JSONEncoderAPI to convertEmployeeobject intoDataobject - Use
UserDefaultsAPI to storeDataobject 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 toJSONEncoder'sencodeAPI can throw an error. We are wrapping it up indo-catchand printing an error for debugging in case code results into an error
Decoding an Object
- Since
Employeestruct already confirms toCodable, it automatically conforms toDecodableprotocol as per Apple's documentation. (typealias Codable = Decodable & Encodable) - Use
employeeDataKeykey to retrieveDataobject fromUserDefaultsstorage - Use
JSONDecoderAPI to decode extracted data object intoEmployeeobject - Wrap the call to
JSONDecoderAPI intodo-catchblock since decoding may throw an error if API cannot successfully decodeDatainto an instance of passedstruct
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.

