1

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) {
        // ???
    }


}

1 Answer 1

11

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]

Update for Swift 3 (Xcode 8):

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.
        if let ptr = decoder.decodeBytes(forKey: "values", returnedLength: &count) {
            // If we convert it to a buffer pointer of the appropriate type and count ...
            let numValues = count / MemoryLayout<UInt64>.stride
            ptr.withMemoryRebound(to: UInt64.self, capacity: numValues) {
                let buf = UnsafeBufferPointer<UInt64>(start: UnsafePointer($0), count: numValues)
                // ... then the Array creation becomes easy.
                values = Array(buf)
            }
        }
    }

    public func encode(with coder: NSCoder) {
        // This encodes both the number of bytes and the data itself.
        let numBytes = values.count * MemoryLayout<UInt64>.stride
        values.withUnsafeBufferPointer {
            $0.baseAddress!.withMemoryRebound(to: UInt8.self, capacity: numBytes) {
                coder.encodeBytes($0, length: numBytes, forKey: "values")
            }
        }
    }
}


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

let dec = NSKeyedUnarchiver.unarchiveObject(with: data) as! MyObject
print(dec.values) // [1, 2, 3, 18446744073709551615]
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks a lot. Is there a way to further optimise the output NSData for size? Or would I have to write my own subclass of NSCoder for that (or maybe use this one)?
@Jean-PhilippePellet: I inspected the archived data and noticed that the UInt64 array is really stored as a length byte plus 4*8 bytes, so unless you "compress" the data somehow you cannot reduce it further. Archiving the object without the array already results in 226 bytes. I have no experience with alternative coders.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.