thisIsTheFoxe
thisIsTheFoxe

Reputation: 1713

NSKeyedUnarchiver data in wrong format

I'm using ARKit and GameKitMatches so I can't use Codable (afaik) because MCPeerID as well as ARWorldMap aren't codable, to get that out of the way first.

So I'm using NSCoding and NSSecureCoding but for some reason I always catch the error:

The data couldn’t be read because it isn’t in the correct format.

...even tho I literally just created it. I also tried to use NSKeyedUnarchiver.unarchivedObject(ofClasses: classes but that threw an unexpected-nil in my init.

Here's a playground I made showing the problem:

class CodingData: NSObject, NSCoding, NSSecureCoding {
    static var supportsSecureCoding = true
    var dic: [String: Int]!
    var i: Int!

    func encode(with coder: NSCoder) {
        coder.encode(i, forKey: "i")
        coder.encode(dic, forKey: "dic")
    }

    required convenience init?(coder: NSCoder) {
        let anInt = coder.decodeObject(forKey: "i") as! Int
        let anDic = coder.decodeObject(forKey: "dic") as! [String: Int]
        self.init(dic: anDic, i: anInt)
    }

    init(dic: [String: Int], i: Int){
        self.dic = dic
        self.i = i
    }
}

do{
    let test = CodingData(dic: [:], i: 0)
    //let classes = [NSDictionary.self, NSNumber.self]
    let testData = try NSKeyedArchiver.archivedData(withRootObject: test, requiringSecureCoding: true)
    let emptyDic = try NSKeyedUnarchiver.unarchivedObject(ofClass: CodingData.self, from: testData)
    // Error here ^^^^^^
}catch{
    error.localizedDescription
}

BTW, not sure if it matters but while trying to debug the coder in the init it always said (prob. just a bug):

error: <EXPR>:1:1: error: non-nominal type '$__lldb_context' (aka 'Self') cannot be extended
extension $__lldb_context {
^         ~~~~~~~~~~~~~~~

error: <EXPR>:19:27: error: value of type 'Self' has no member '$__lldb_wrapped_expr_28'
    $__lldb_injected_self.$__lldb_wrapped_expr_28(
    ~~~~~~~~~~~~~~~~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~~

Upvotes: 3

Views: 1034

Answers (1)

matt
matt

Reputation: 535989

You've made a great effort, but there is so much wrong with your code that I think the easiest way to answer is just to correct it:

class CodingData: NSObject, NSSecureCoding {
    static var supportsSecureCoding = true
    var dic: [String: Int]
    var i: Int

    func encode(with coder: NSCoder) {
        coder.encode(i as NSNumber, forKey: "i")
        coder.encode(dic as NSDictionary, forKey: "dic")
    }

    required init?(coder: NSCoder) {
        let anInt = coder.decodeObject(of: NSNumber.self, forKey: "i")
        let anDic = coder.decodeObject(of: NSDictionary.self, forKey: "dic")
        self.dic = anDic as! [String:Int]
        self.i = anInt as! Int
    }

    init(dic: [String: Int], i: Int){
        self.dic = dic
        self.i = i
    }
}

It will work now:

let test = CodingData(dic: [:], i: 0)
let testData = try NSKeyedArchiver.archivedData(withRootObject: test, requiringSecureCoding: true)
let emptyDic = try NSKeyedUnarchiver.unarchivedObject(ofClass: CodingData.self, from: testData)
print(emptyDic?.dic as Any, emptyDic?.i as Any) // Optional([:]) Optional(0)

So your four chief mistakes were:

  • Do not declare an instance property as an implicitly unwrapped Optional (!)

  • init?(coder:) is not a convenience initializer

  • To do secure coding, you must decode each encoded property using secure coding (decodeObject(of:forKey:))

  • Only NSSecureCoding types can be encoded using secure coding, so Swift types must be cast to their Objective-C equivalents

Upvotes: 6

Related Questions