swift: nscoding decodeObject with nil all the time
Asked Answered
S

4

13

I got the following codes on writing an object named Packet and send to the other side through Multipeer connectivity. However, I got the following error whenever it try to decode the encoded object.

  class Packet : NSObject, NSCoding {

  var tmp1: Double = 0
  var tmp2: Double = 0

  struct PropertyKey {
    static let tmp1Key = "tmp1Key"
    static let tmp2Key = "tmp2Key"
  }


  init(tmp1: Double, tmp2: Double) {
    self.tmp1 = tmp1
    self.tmp2 = tmp2
    super.init()
  }

  deinit {
  }

  required convenience init(coder aDecoder: NSCoder) {
    debugPrint("initcoder")
    let tmp1 = aDecoder.decodeObject(forKey: PropertyKey.tmp1Key) as! Double // crash here
    let tmp2 = aDecoder.decodeObject(forKey: PropertyKey.tmp2Key) as! Double
    self.init(tmp1: tmp1, tmp2: tmp2)
  }

  public func encode(with aCoder: NSCoder) {
    debugPrint("encodeCoder")
    aCoder.encode(tmp1, forKey: PropertyKey.tmp1Key)
    aCoder.encode(tmp2, forKey: PropertyKey.tmp2Key)
  }
}

The error I got is ---- Print out from me ---- "initcoder" fatal error: unexpectedly found nil while unwrapping an Optional value 2016-09-30 13:32:55.901189 Connection[323:33022] fatal error: unexpectedly found nil while unwrapping an Optional value

But when I construct the object, all the values are set. I contracted a Packet object with no problem.

---- Additional information ------ I used the following codes for encode and decode the data when send to the other side through multiplier connectivity.

 func dataForPacket(packet: Packet) -> Data {
    let data = NSMutableData()
    let archiver = NSKeyedArchiver(forWritingWith: data)
    archiver.encode(packet, forKey: "Packet")
    archiver.finishEncoding()
    debugPrint("dataForPacket \(data) \(packet)")
    return data as Data
  }

  func packetForData(_ data: Data) -> Packet {
    debugPrint("packetForData")
    let unarchiver = NSKeyedUnarchiver(forReadingWith: data)

    let packet: Packet = unarchiver.decodeObject(forKey: "Packet") as! Packet 
     // crash here (as this will call the init of the Packet class)
    debugPrint("packetForData \(packet)")
    return packet
  }

I wonder what can cause the error. Thanks.

Sol answered 30/9, 2016 at 5:47 Comment(5)
You need to use decodeDouble for keyOxa
let tmp1 = aDecoder.decodeDouble(forKey: PropertyKey.tmp1Key)Oxa
As the above comments have mentioned you have to use specific decode for integers, float, bool, doubles etc..Australian
Thanks a lot!! It solved my problem!Sol
I found another issue: When my class Packet has class variables of other types (e.g., Arrays, UIViews) and then I used decodeDouble for tmp1 and tmp2, but then use decodeObject for the others. It then crash and said that tmp1 and tmp2 are not of type Double (while they are really of type Double). and it no longer crash when I change them to decodeObject. Why it is the case?Sol
G
15

Swift 3:

I was encoding a double value. Use decoder.containsValue to check if a value was encoded.

And use decodeDouble(forKey:). Not decodeObject(forKey:)

 var dateTime: TimeInterval = SOME_TIME_INTERVAL (Double)//Can be saved(encoded) previously, and not encoded as well.
 aCoder.encode(dateTime, forKey: "dateTime")
 if decoder.containsValue(forKey: "dateTime") {
            dateTime = decoder.decodeDouble(forKey: "dateTime")
        }
Garfield answered 28/3, 2017 at 10:8 Comment(1)
Note: When decoding Strings, use decodeObject(forKey:)Jacqualinejacquard
B
5

Swift 4.2:

When using decodeObject(forKey:) to decode primitive types, it returns nil. Because the overload of encode(_:forKey:) takes an Int directly and writes it NSNumber type, and you cannot decode it as an object.

decodeInteger(forKey:) reads it out as an Int properly and returns it. You can use decodeInteger(forKey:), Int(decodeInt32(forKey:)) or Int(decodeInt64(forKey:)) instead.

Biofeedback answered 16/1, 2019 at 9:38 Comment(0)
J
1

When encoding or decoding primitive types such as Double and Bool you must use the designated methods decodeDouble:forKey and decodeBool:forKey respectively otherwise they will fail to decode and return nil.

Jahdai answered 2/8, 2018 at 14:31 Comment(0)
U
1

In my case, the class I was trying to decode was an NSDictionary and it kept coming back as nil after I refactored my code to use non-deprecated functions in the NSKeyedUnarchiver in Swift 5.3.

When I tried decoding the object, it was always nil, even though the following code returned true:

unarchiver.containsValue(forKey: saveKey)

I found the issue. If I use the new non-deprecated function, it worked:

WORKING Code:

let unarchiver = try NSKeyedUnarchiver(forReadingWith: data)  // forReadingWith now has a deprecated warning.. ?? but the alternative does not work.
var unarchived = unarchiver.decodeObject(forKey: saveKey) // nil

BROKEN Code - does not work but uses the non-deprecated function:

let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
var unarchived = unarchiver.decodeObject(forKey: saveKey) // not nil - works
Unquote answered 23/1, 2021 at 17:56 Comment(3)
Did you ever find a solution that involved using the modern (non-deprecated) APIs? Wondering because we want to read previously saved data and I'm still seeing nil. @UnquoteBeckiebeckley
Make sure you’re writing with a non-deprecated function tooUnquote
decodeTopLevelObject fixed the issue for meBeckiebeckley

© 2022 - 2024 — McMap. All rights reserved.