8

Here is my Codable class:

class SensorOutput: Codable {

    var timeStamp: Date?

    var gyroX: Double?
    var gyroY: Double?
    var gyroZ: Double?

    var accX: Double?
    var accY: Double?
    var accZ: Double?

    var magX: Double?
    var magY: Double?
    var magZ: Double?

    init() {}
}

Here I try to write and read the object of that class to file:

    let myData = SensorOutput()
    myData.timeStamp = Date()
    myData.gyroX = 0.0
    myData.gyroY = 0.0
    myData.gyroZ = 0.0
    myData.accX = 0.0
    myData.accY = 0.0
    myData.accZ = 0.0
    myData.magX = 0.0
    myData.magY = 0.0
    myData.magZ = 0.0

    NSKeyedArchiver.archiveRootObject(myData, toFile: filePath)

    if let Data = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? SensorOutput {
        print (Data)
    }

This gives an error during the process of archiving: Error screenshot

PS: filePath I receiving in such way:

var filePath: String {
    //1 - manager lets you examine contents of a files and folders in your app; creates a directory to where we are saving it
    let manager = FileManager.default
    //2 - this returns an array of urls from our documentDirectory and we take the first path
    let url = manager.urls(for: .documentDirectory, in: .userDomainMask).first
    print("this is the url path in the documentDirectory \(String(describing: url))")
    //3 - creates a new path component and creates a new file called "Data" which is where we will store our Data array.
    return (url!.appendingPathComponent("Data").path)
}

Reading/writing works with Int or Double and with other supported types, but not with my type. What’s wrong?

M Reza
  • 18,350
  • 14
  • 66
  • 71

3 Answers3

15

Although @matt's answer contains the essential information to solve your problem, it might not be obvious how to apply that information if you're new to Swift and iOS programming.

You tried using NSKeyedArchiver.archiveRootObject(_:toFile:), which is a class method, so you didn't have to create an instance of NSKeyedArchiver. Since encodeEncodable(_:forKey:) is an instance method, not a class method, you need to create an instance of NSKeyedArchiver to use it. You also need to create an NSMutableData for the archiver to append bytes to, and you have to call finishEncoding after encoding your object.

    let sensorOutput = SensorOutput()
    sensorOutput.timeStamp = Date()

    let mutableData = NSMutableData()
    let archiver = NSKeyedArchiver(forWritingWith: mutableData)
    try! archiver.encodeEncodable(sensorOutput, forKey: NSKeyedArchiveRootObjectKey)
    archiver.finishEncoding()

    // You can now write mutableData to a file or send it to your server
    // or whatever.

Similarly, you tried using NSKeyedUnarchiver.unarchiveObject(withFile:), which is a class method, but you need to use decodeDecodable(_:forKey:) or decodeTopLevelDecodable(_:forKey:), which are instance methods. So you need to read in the archive data and use it to make an instance of NSKeyedUnarchiver.

// Read in the data from a file or your server or whatever.
// I'll just make an immutable copy of the archived data for this example.
let data = mutableData.copy() as! Data

let unarchiver = NSKeyedUnarchiver(forReadingWith: data)
do {
    if let sensorOutputCopy = try unarchiver.decodeTopLevelDecodable(SensorOutput.self, forKey: NSKeyedArchiveRootObjectKey) {
        print("deserialized sensor output: \(sensorOutputCopy)")
    }
} catch {
    print("unarchiving failure: \(error)")
}

(I prefer the decodeTopLevelDecodable method instead of decodeDecodable because it throws a Swift error instead of crashing if the archive is corrupt.)

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Thank you for your detailed answer, all works. But how to get URL of NSMutableData or how to send the saved file from Apple Watch to iPhone using transferFile(_:metadata:) ? – Degtiarev Aleksei Apr 21 '18 at 04:41
  • You can write the `NSMutableData` to a file using one of its `write(toFile:atomically:)`, `write(toFile:options:)`, `write(to:atomically:)`, or `write(to:options:)` methods. (It inherits these methods from `NSData`). – rob mayoff Apr 21 '18 at 04:44
9

The error message is telling you that SensorOutput needs to derive from NSObject.

However, the root problem is that you are using NSKeyedArchiver wrong. You are calling archiveRootObject, acting as if this type adopted NSCoding. It doesn't. It adopts Codable. If you are going to use the fact that this type is Codable, you call NSKeyedArchiver encodeEncodable(_:forKey:) to encode and NSKeyedUnarchiver decodeDecodable(_:forKey:) to decode.

matt
  • 515,959
  • 87
  • 875
  • 1,141
0

You were using it the wrong way. It adopts Codable protocol not NSCoding one. (As @matt pointed out).

You could solve it like that:

let myData = SensorOutput()
let data = try! PropertyListEncoder().encode(myData)
NSKeyedArchiver.archivedData(withRootObject: data)
Jakub Truhlář
  • 20,070
  • 9
  • 74
  • 84