Structs? on classes only in NSCoding

I tried to implement NSCoding in my app. There are some simply arrays of String, some simple structs, arrays of those structs, dictionaries who’s values are structs, and a few enums. The array of Strings archived with no effort. But when I got to my array of structs, it all fell apart. I had to convert all my structs to classes and my enums had to be converted to rawValues.

Is there no simple way to have left my data model as structs and still done archiving?

There is more than one way to do this. One method is to use extensions.

I have adopted the following example from Swift And Painless.


// A simple struct
struct Aircraft {
    let latitude  : String
    let longitude : String
    let altitude  : UInt

Extension for Archiving

// Extension for archiving
extension Aircraft {
    static func archive (aircraft: Aircraft) {
        let aircraftObject = ArchivableObject (aircraft: aircraft)
        NSKeyedArchiver.archiveRootObject (aircraftObject, toFile: ArchivableObject.path())
    static func unarchive () -> Aircraft? {
        let aircraftObject = NSKeyedUnarchiver.unarchiveObject (withFile: ArchivableObject.path()) as? ArchivableObject
        return aircraftObject?.aircraft

    class ArchivableObject: NSObject, NSCoding {
        var aircraft: Aircraft?
        init (aircraft: Aircraft) {
            self.aircraft = aircraft
        class func path() -> String {
            let documentsPath = NSSearchPathForDirectoriesInDomains (FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
            let path = documentsPath?.appending ("/AircraftArchive")
            return path!
        required init? (coder aDecoder: NSCoder) {
            guard let latitude = aDecoder.decodeObject (forKey: "latitude") as? String else {
                return nil
            guard let longitude = aDecoder.decodeObject (forKey: "longitude") as? String else {
                return nil
            guard let altitude = aDecoder.decodeObject (forKey: "altitude") as? UInt else {
                return nil
            aircraft = Aircraft (latitude:latitude, longitude:longitude, altitude:altitude)
        func encode (with aCoder: NSCoder) {
            aCoder.encode (aircraft!.latitude,  forKey:"latitude")
            aCoder.encode (aircraft!.longitude, forKey:"longitude")
            aCoder.encode (aircraft!.altitude,  forKey:"altitude")


// Testing
let jibber = Aircraft (latitude:"S 34.9285", longitude:"E 138.6007", altitude:16_000_000)
print ("jibber: \(jibber)")

Aircraft.archive (aircraft: jibber)

let unarchivedJibber = Aircraft.unarchive()!
print ("unarchivedJibber: \(unarchivedJibber)")

assert (jibber.latitude   == unarchivedJibber.latitude)
assert (jibber.longitude  == unarchivedJibber.longitude)
assert (jibber.altitude   == unarchivedJibber.altitude)

Excellent. Thank you. It is a little extra coding, but it doesn’t involved completely changing the nature of my data model.