swift 3 encoding

I am having problem with encoding/decoding with KeyedArchiver in Swift 3. I managed to reproduce the problem with this small class for which an instance can be encoded with a key but does not seem to be decoded with the same key.


Any suggestion or hint will be appreciated...

Here is the full code and the stack trace when it fails in decoding

----

import Foundation

class MyClass : NSObject, NSCoding {

var a : Int

init(a:Int) {

self.a = a

}

required init(coder aDecoder: NSCoder) {

self.a = aDecoder.decodeObject(forKey: "a") as! Int

}

func encode(with aCoder: NSCoder) {

aCoder.encode(a, forKey: "a")

}

override var description: String {

return "\(a)"

}

}

let m1=MyClass(a: 8)

print("m1=\(m1)")

let archivedClass=NSKeyedArchiver.archivedData(withRootObject: m1)

print("archivedClass=\(archivedClass)")

let deArchivedClass=NSKeyedUnarchiver.unarchiveObject(with: archivedClass)

print("deArchivedClass=\(deArchivedClass as AnyObject)")


/* output after stopping at self.a = aDecoder.decodeObject(forKey: "a") as! Int

m1=8

archivedClass=250 bytes

fatal error: unexpectedly found nil while unwrapping an Optional value

Current stack trace:

0 TestEncoding 0x000000010031bfe0 swift_reportError + 132

1 TestEncoding 0x0000000100339390 _swift_stdlib_reportFatalError + 61

2 TestEncoding 0x0000000100002fd0 specialized specialized StaticString.withUTF8Buffer<A> ((UnsafeBufferPointer<UInt8>) -> A) -> A + 656

3 TestEncoding 0x00000001002a93c0 partial apply for (_fatalErrorMessage(StaticString, StaticString, StaticString, UInt, flags : UInt32) -> Never).(closure #2) + 109

4 TestEncoding 0x0000000100002fd0 specialized specialized StaticString.withUTF8Buffer<A> ((UnsafeBufferPointer<UInt8>) -> A) -> A + 656

5 TestEncoding 0x0000000100263d80 specialized _fatalErrorMessage(StaticString, StaticString, StaticString, UInt, flags : UInt32) -> Never + 96

6 TestEncoding 0x00000001000027a0 MyClass.init(coder : NSCoder) -> MyClass + 329

7 TestEncoding 0x0000000100002a20 @objc MyClass.init(coder : NSCoder) -> MyClass + 45

8 Foundation 0x00007fff8ec2e9ac _decodeObjectBinary + 2743

9 Foundation 0x00007fff8ec2e78c _decodeObject + 281

10 Foundation 0x00007fff8ec2d7ba +[NSKeyedUnarchiver unarchiveObjectWithData:] + 89

11 TestEncoding 0x0000000100001ca0 main + 1172

12 libdyld.dylib 0x00007fff8e5625ac start + 1

*/

Replies

You can fix this by changing this line:

aCoder.encode(a, forKey: "a")

to this:

aCoder.encode(a as NSNumber, forKey: "a")

The first parameter to

encode(_:forKey:)
is of type
Any?
, so passing in
a
results in a boxed Swift
Int
, which isn’t helpful. The
as NSNumber
forces an object representation of
a
into the archive.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for the tip which works fine for simple types, but my real problem is that I would like to encode/decode a dictionary or an array of tuples

Here is a variant of my previous example which stops at the encoding stage...


/// exemple adapted from https://forums.developer.apple.com/thread/7716

typealias state=[Character:Int] // idealy it would be [(Character,Int)]

class MyClass : NSObject, NSCoding {

var a : state


init(a:state) {

self.a = a

}


required init(coder aDecoder: NSCoder) {

self.a = aDecoder.decodeObject(forKey: "a") as! state

}


func encode(with aCoder: NSCoder) {

aCoder.encode(a as NSDictionary, forKey: "a")

}


override var description: String {

return "\(a)"

}

}

let m1=MyClass(a: ["a":8,"b":6,"c":7])

print("m1=\(m1)")

let archivedClass=NSKeyedArchiver.archivedData(withRootObject: m1)

print("archivedClass=\(archivedClass)")

let deArchivedClass=NSKeyedUnarchiver.unarchiveObject(with: archivedClass)

print("deArchivedClass=\(deArchivedClass as AnyObject)")

/* output after stopping at aCoder.encode(a as NSDictionary, forKey: "a")

m1=["b": 6, "a": 8, "c": 7]

2017-01-15 17:13:31.305532 TestEncoding[11817:1184505] -[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x100a0c290

2017-01-15 17:13:31.310967 TestEncoding[11817:1184505] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x100a0c290'

*** First throw call stack:

(

0 CoreFoundation 0x00007fffcc9f3e7b __exceptionPreprocess + 171

1 libobjc.A.dylib 0x00007fffe15decad objc_exception_throw + 48

2 CoreFoundation 0x00007fffcca75cb4 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132

3 CoreFoundation 0x00007fffcc965fb5 ___forwarding___ + 1061

4 CoreFoundation 0x00007fffcc965b08 _CF_forwarding_prep_0 + 120

5 Foundation 0x00007fffce3d6a2a _encodeObject + 1241

6 Foundation 0x00007fffce3d7fdc -[NSKeyedArchiver _encodeArrayOfObjects:forKey:] + 460

7 Foundation 0x00007fffce3e91d1 -[NSDictionary(NSDictionary) encodeWithCoder:] + 850

8 Foundation 0x00007fffce3d6a2a _encodeObject + 1241

9 TestEncoding 0x00000001000028b9 _TFC12TestEncoding7MyClass6encodefT4withCSo7NSCoder_T_ + 249

10 TestEncoding 0x000000010000292a _TToFC12TestEncoding7MyClass6encodefT4withCSo7NSCoder_T_ + 58

11 Foundation 0x00007fffce3d6a2a _encodeObject + 1241

12 Foundation 0x00007fffce412562 +[NSKeyedArchiver archivedDataWithRootObject:] + 156

13 TestEncoding 0x0000000100001bf4 main + 852

14 libdyld.dylib 0x00007fffe1ec2255 start + 1

15 ??? 0x0000000000000001 0x0 + 1

)

libc++abi.dylib: terminating with uncaught exception of type NSException

*/

I think

encode(_ intv: Int, forKey key: String)
will be called in Guy's posted code. In which case,
decodeInteger(forKey:)
can be used to unarchive an Int.


required init(coder aDecoder: NSCoder) {
        self.a = aDecoder.decodeInteger(forKey: "a")
    }

func encode(with aCoder: NSCoder) {
        aCoder.encode(a, forKey: "a")
    }

… my real problem is that I would like to encode/decode a dictionary or an array of tuples

The keyed archiver is only able to deal with Objective-C compatible object types. So you wan’t be able to encode a tuple directly because there’s no way to represent that as an Objective-C object. You could use a small custom class for this, or just expand the tuple into an NSArray.

Similarly, for dictionaries you have to make sure the dictionary ends up being compatible with NSDictionary.

Here is a variant of my previous example which stops at the encoding stage...

btw It would help if you inserted code as code via the

<>
icon.

The problem you’re having with the code as posted in that

state
uses
Character
, and
Character
is not Objective-C compatible. If you change the declaration to this:
typealias state=[String:Int] // idealy it would be  [(Character,Int)]

your code runs just fine.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for your explanation about the use of Objective-C compatible object types which enabled me to understand why my previous attempts at KeyedArchiver worked fine (I had used [String:Int]). But when I simplified my code to use [(Character,Int)] instead, I started to get error messages without any hint about what was the problem.


Let's hope that at one time, Swift will eventually provide an archiving facility for all its types.