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.

user6539552
  • 1,331
  • 2
  • 19
  • 39
  • 2
    You need to use decodeDouble for key – Leo Dabus Sep 30 '16 at 06:09
  • 3
    `let tmp1 = aDecoder.decodeDouble(forKey: PropertyKey.tmp1Key)` – Leo Dabus Sep 30 '16 at 06:10
  • 1
    As the above comments have mentioned you have to use specific decode for integers, float, bool, doubles etc.. – mn1 Sep 30 '16 at 06:57
  • Thanks a lot!! It solved my problem! – user6539552 Sep 30 '16 at 07:09
  • 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? – user6539552 Oct 02 '16 at 17:24

4 Answers4

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")
        }
Naloiko Eugene
  • 2,453
  • 1
  • 28
  • 18
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.

neycyanshi
  • 61
  • 1
  • 2
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.

M3nd3z
  • 316
  • 2
  • 12
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
TheJeff
  • 3,665
  • 34
  • 52
  • 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`. @TheJeff – Daniel Sep 16 '21 at 01:05
  • 1
    Make sure you’re writing with a non-deprecated function too – TheJeff Sep 16 '21 at 01:40
  • `decodeTopLevelObject` fixed the issue for me – Daniel Sep 17 '21 at 20:28