NSCoding And Swift Structs
As you all know NSCoding doesn’t
work with Swift structs. NSCoding only works
for classes inheriting from NSObject. But the
next (or current) big thing are structs. Value types all the way. So we
need a way to archive and unarchive instances of structs.
Janie wrote about how they solved it at Sonoplot where she works.
Tl;dr: They define a protocol that has two methods: one for getting
an NSDictionary out of
a struct and one for initializing the struct with an NSDictionary. The
NSDictionary is then serialized using NSKeyedArchiver. The
beauty of this approach is that each struct conforming to that protocol
can be serialized.
I came up with an other approach. It just popped up in my mind. And even after experimenting with it and implementing a little toy project I’m still not sure if this is a good idea. It’s definitely not as beautiful as the approach mentioned above. I put it here to let you decide.
Let’s say we have a person struct:
struct Person {
let firstName: String
let lastName: String
}So we can’t make this conforming to NSCoding but we can
add a class within the Person struct that conforms to NSCoding:
extension Person {
class HelperClass: NSObject, NSCoding {
var person: Person?
init(person: Person) {
self.person = person
super.init()
}
class func path() -> String {
let documentsPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).first
let path = documentsPath?.stringByAppendingString("/Person")
return path!
}
required init?(coder aDecoder: NSCoder) {
guard let firstName = aDecoder.decodeObjectForKey("firstName") as? String else { person = nil; super.init(); return nil }
guard let laseName = aDecoder.decodeObjectForKey("lastName") as? String else { person = nil; super.init(); return nil }
person = Person(firstName: firstName, lastName: laseName)
super.init()
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(person!.firstName, forKey: "firstName")
aCoder.encodeObject(person!.lastName, forKey: "lastName")
}
}
}So, what is happening here. We have just added a class within the
Person struct that conforms to NSCoding which means
it implements the methods init?(coder aDecoder: NSCoder) and
encodeWithCoder(aCoder: NSCoder). The class has a property of type
Person and in encodeWithCoder(aCoder: NSCoder)
it writes the values of the properties of the person
instance to the coder and in init?(coder aDecoder: NSCoder) it
reads those values again from the decoder and creates
a new person instance.
What’s left is to add encoding and decoding methods to the Person struct:
struct Person {
let firstName: String
let lastName: String
static func encode(person: Person) {
let personClassObject = HelperClass(person: person)
NSKeyedArchiver.archiveRootObject(personClassObject, toFile: HelperClass.path())
}
static func decode() -> Person? {
let personClassObject = NSKeyedUnarchiver.unarchiveObjectWithFile(HelperClass.path()) as? HelperClass
return personClassObject?.person
}
}With this code we create a HelperClass object to make the archiving and unarchiving.
The struct is used like this:
let me = Person(firstName: "Dominik", lastName: "Hauser")
Person.encode(me)
let myClone = Person.decode()
firstNameLabel.text = myClone?.firstName
lastNameLabel.text = myClone?.lastNameYou can find the code for this experiment on github.
If you enjoyed this post, then make sure you subscribe to my feed.