[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
Employee
toCodable
protocol. This will allow us to easily encode and decode it back into a custom object - Use the
JSONEncoder
API to convertEmployee
object intoData
object - Use
UserDefaults
API to storeData
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 toJSONEncoder
'sencode
API can throw an error. We are wrapping it up indo-catch
and printing an error for debugging in case code results into an error
Decoding an Object
- Since
Employee
struct already confirms toCodable
, it automatically conforms toDecodable
protocol as per Apple's documentation. (typealias Codable = Decodable & Encodable
) - Use
employeeDataKey
key to retrieveData
object fromUserDefaults
storage - Use
JSONDecoder
API to decode extracted data object intoEmployee
object - Wrap the call to
JSONDecoder
API intodo-catch
block since decoding may throw an error if API cannot successfully decodeData
into 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.