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?.lastName
You can find the code for this experiment on github.
If you enjoyed this post, then make sure you subscribe to my feed.