5 Replies
      Latest reply: Jan 16, 2017 7:28 PM by Guy Lapalme RSS
      Guy Lapalme Level 1 Level 1 (0 points)

        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

        */

        • Re: swift 3 encoding
          eskimo Apple Staff Apple Staff (7,005 points)

          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"

            • Re: swift 3 encoding
              Guy Lapalme Level 1 Level 1 (0 points)

              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

              */

                • Re: swift 3 encoding
                  eskimo Apple Staff Apple Staff (7,005 points)

                  … 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"

                    • Re: swift 3 encoding
                      Guy Lapalme Level 1 Level 1 (0 points)

                      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.

                  • Re: swift 3 encoding
                    goldsdad Level 4 Level 4 (580 points)

                    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")
                        }