Save custom objects into NSUserDefaults
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
NSUserDefaults, now exposed in Swift as UserDefaults, only stores property-list types directly, such as strings, numbers, booleans, arrays, dictionaries, and Data. To save a custom object, you first encode it into Data, then write that data to defaults. For modern Swift code, Codable is the simplest and safest default approach.
Save a Custom Object with Codable
If your type is a plain Swift struct or class with codable properties, use JSONEncoder or PropertyListEncoder to turn it into Data.
To read it back:
This keeps the object model explicit and avoids older Objective-C archiving APIs unless you really need them.
When to Use NSSecureCoding
If you are working with legacy Objective-C types or a class hierarchy based on NSObject, NSSecureCoding is still relevant. In that case, archive the object into Data and store the data in defaults.
And to decode:
Use this pattern when you need compatibility with existing archived objects or Objective-C APIs that already depend on keyed archiving.
Know When UserDefaults Is the Wrong Storage
UserDefaults is designed for small preference-like values, not large object graphs or sensitive records. Encoding a tiny profile or settings object is fine. Storing large caches, images, or complex relational data is not.
If the data is sensitive, prefer the Keychain. If the data is large or relational, use files, SQLite, Core Data, or another persistence layer.
Updating Stored Objects Safely
A practical pattern is to wrap the save and load logic in one place so the rest of the app does not manipulate raw keys.
This reduces duplicated keys and makes migrations easier if the stored format changes later.
Common Pitfalls
One common mistake is trying to store a custom object directly with set(_:forKey:). That only works for property-list-compatible values unless you encode the object first.
Another mistake is treating UserDefaults like a database. It is not optimized for large payloads or complex querying.
Developers also sometimes use NSKeyedArchiver without secure coding enabled. For modern code, prefer Codable when possible and NSSecureCoding when archiving is required.
Finally, changing a model’s properties over time can break decoding if you do not plan for migration. If the schema may evolve, make new fields optional or provide custom decoding logic.
Summary
- Store custom objects in
UserDefaultsby encoding them toDatafirst. - '
Codableis the best default approach for modern Swift types.' - Use
NSSecureCodingfor legacy Objective-C classes or existing archived data. - Keep
UserDefaultsfor small preference-like objects, not large or sensitive datasets. - Centralizing save and load logic makes keys, migrations, and testing much easier.

