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.

Struct

// 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
            super.init()
        }
        
        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)
            
            super.init()
        }
        
        func encode (with aCoder: NSCoder) {
            aCoder.encode (aircraft!.latitude,  forKey:"latitude")
            aCoder.encode (aircraft!.longitude, forKey:"longitude")
            aCoder.encode (aircraft!.altitude,  forKey:"altitude")
        }
    }
}

Testing

// 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.