Saving custom Swift class with NSCoding to UserDefaults
In Swift 4 or higher, Use Codable.
In your case, use following code.
class Blog: Codable {
var blogName: String?
}
Now create its object. For example:
var blog = Blog()
blog.blogName = "My Blog"
Now encode it like this:
if let encoded = try? JSONEncoder().encode(blog) {
UserDefaults.standard.set(encoded, forKey: "blog")
}
and decode it like this:
if let blogData = UserDefaults.standard.data(forKey: "blog"),
let blog = try? JSONDecoder().decode(Blog.self, from: blogData) {
}
The first problem is you have to ensure that you have a non-mangled class name:
@objc(Blog)
class Blog : NSObject, NSCoding {
Then you have to encode the object (into an NSData) before you can store it into the user defaults:
ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(blog), forKey: "blog")
Similarly, to restore the object you'll need to unarchive it:
if let data = ud.objectForKey("blog") as? NSData {
let unarc = NSKeyedUnarchiver(forReadingWithData: data)
unarc.setClass(Blog.self, forClassName: "Blog")
let blog = unarc.decodeObjectForKey("root")
}
Note that if you're not using it in the playground it's a little simpler as you don't have to register the class by hand:
if let data = ud.objectForKey("blog") as? NSData {
let blog = NSKeyedUnarchiver.unarchiveObjectWithData(data)
}
As @dan-beaulieu suggested I answer my own question:
Here is the working code now:
Note: Demangling of the class name was not necessary for the code to work in Playgrounds.
import Foundation
class Blog : NSObject, NSCoding {
var blogName: String?
override init() {}
required init(coder aDecoder: NSCoder) {
if let blogName = aDecoder.decodeObjectForKey("blogName") as? String {
self.blogName = blogName
}
}
func encodeWithCoder(aCoder: NSCoder) {
if let blogName = self.blogName {
aCoder.encodeObject(blogName, forKey: "blogName")
}
}
}
let ud = NSUserDefaults.standardUserDefaults()
var blog = Blog()
blog.blogName = "My Blog"
ud.setObject(NSKeyedArchiver.archivedDataWithRootObject(blog), forKey: "blog")
if let data = ud.objectForKey("blog") as? NSData {
let unarc = NSKeyedUnarchiver(forReadingWithData: data)
let newBlog = unarc.decodeObjectForKey("root") as Blog
}
Here is a complete solution for Swift 4 & 5.
First, implement helper methods in UserDefaults
extension:
extension UserDefaults {
func set<T: Encodable>(encodable: T, forKey key: String) {
if let data = try? JSONEncoder().encode(encodable) {
set(data, forKey: key)
}
}
func value<T: Decodable>(_ type: T.Type, forKey key: String) -> T? {
if let data = object(forKey: key) as? Data,
let value = try? JSONDecoder().decode(type, from: data) {
return value
}
return nil
}
}
Say, we want to save and load a custom object Dummy
with 2 default fields. Dummy
must conform to Codable
:
struct Dummy: Codable {
let value1 = "V1"
let value2 = "V2"
}
// Save
UserDefaults.standard.set(encodable: Dummy(), forKey: "K1")
// Load
let dummy = UserDefaults.standard.value(Dummy.self, forKey: "K1")