Jean-Philippe Pellet Jean-Philippe Pellet - 3 months ago 9
Swift Question

How can I get an NSCoder to encode/decode a Swift array of structs?

I have an object that must conform to

NSCoding
and that holds an array of
UInt64
values. How can I encode/decode it with an
NSCoder
at all? Bonus question: how can I encode it most compactly? (It has to go into saved Game Center state data, whose size is limited.)

Ideally, I just want to write an
Int
which is the size
n
of the array, and then write
n
times the 64 bits of a
UInt64
, and read it similarly. Can I do this?

coder.encodeObject(values, forKey: "v")
doesn't work.

class MyObject: NSCoding {

private var values: [UInt64]

// …

// MARK: - NSCoding

required init(coder decoder: NSCoder) {
// ???
}

func encodeWithCoder(coder: NSCoder) {
// ???
}


}

Answer

Here is a possible solution that encodes the UInt64 array as an array of bytes. It is inspired by the answers to How to serialize C array with NSCoding?.

class MyObject: NSObject, NSCoding {

    var values: [UInt64] = []

    init(values : [UInt64]) {
        self.values = values
    }

    // MARK: - NSCoding
    required init(coder decoder: NSCoder) {
        super.init()
        var count = 0
        // decodeBytesForKey() returns an UnsafePointer<UInt8>, pointing to immutable data.
        let ptr = decoder.decodeBytesForKey("values", returnedLength: &count)
        // If we convert it to a buffer pointer of the appropriate type and count ...
        let buf = UnsafeBufferPointer<UInt64>(start: UnsafePointer(ptr), count: count/sizeof(UInt64))
        // ... then the Array creation becomes easy.
        values = Array(buf)
    }

    func encodeWithCoder(coder: NSCoder) {
        // This encodes both the number of bytes and the data itself.
        coder.encodeBytes(UnsafePointer(values), length: values.count * sizeof(UInt64), forKey: "values")
    }
}

Test:

let obj = MyObject(values: [1, 2, 3, UInt64.max])
let data = NSKeyedArchiver.archivedDataWithRootObject(obj)

let dec = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! MyObject
print(dec.values) // [1, 2, 3, 18446744073709551615]