16

I have migrated my project to Swift 3 and NSKeyedArchiver does not work. I actually have a runtime error when trying to decode object like this:

let startDayTime = aDecoder.decodeObject(forKey: Key.startDayTime) as! Int

It worked perfectly in Swift 2.2 in Xcode 7.3. Has anybody else faced such troubles?

P.S. I have this error on both Simulator and Device.

pkamb
  • 33,281
  • 23
  • 160
  • 191
Arthur Shkil
  • 362
  • 2
  • 12
  • Please add your error output. – yennsarah Jun 30 '16 at 13:29
  • 2
    It could be either that the object was `nil` or that the cast to `Int` failed. You should look at the result of `decodeObject` before the forced casting and see which is the problem. Also, FYI, when dealing with integers, you can use `decodeInteger(forKey: )`. – Rob Jun 30 '16 at 14:01
  • You should add solution as an answer – Bhumit Mehta Jul 05 '16 at 07:41
  • 1
    Apple is playing with our nerves. I have been trying to debug this for a couple of hours now. It seems unbelievable, but they had to make decodeObjece as? Int fail!!!! – zirinisp Oct 03 '16 at 15:45

4 Answers4

16

It appears that this only happens on the Swift 2 to Swift 3 update boundary when a NSData blob archived with a NSKeyedArchiver in Swift 2 is opened with a NSKeyedUnarchiver in Swift 3. My guess is that on Swift 2, the Bool and Int are encoded as NSNumber, but in Swift 3, they are encoded as raw Bool and Int types. I believe the following test supports this claim:

This works in Swift 3 to unarchive a Bool encoded in Swift 2, but returns nil if the Bool was encoded in Swift 3:

let visible = aDecoder.decodeObject(forKey: "visible") as? Bool

This works in Swift 3 to unarchive a Bool encoded in Swift 3, but crashes if the Bool was encoded in Swift 2:

let visible = aDecoder.decodeBool(forKey: "visible")

My solution is:

let visible = aDecoder.decodeObject(forKey: "visible") as? Bool ?? aDecoder.decodeBool(forKey: "visible")
James Kuang
  • 10,710
  • 4
  • 28
  • 38
  • thanks, this worked for me. such a subtle error, they really need to document stuff like this and save devs time. – Ryhan Jan 29 '17 at 19:09
  • This should be the best answer because it safely allows customers to upgrade apps running swift 2 to 3. – pbush25 Feb 02 '17 at 22:48
8

I solved this problem by using:

decodeInteger(forKey key: String)

instead of:

decodeObject(forKey key: String)

For some reason AnyObject does not cast to Integer in Swift 3, though it did in Swift 2.2

pkamb
  • 33,281
  • 23
  • 160
  • 191
Arthur Shkil
  • 362
  • 2
  • 12
  • Want to mention the same for Bool. – James Kuang Oct 13 '16 at 02:10
  • I'm still having issues if updating from encodeObject (swift 2), decodeInteger (swift 3) still crashes my app :( – George Nov 12 '16 at 03:13
  • I just found this post. I am now dealing with a somewhat similar problem here: http://stackoverflow.com/questions/41224991/nskeyedarchiver-nskeyedunarchiver-swift-3-0. Were you using NSKeyedUnarchiver as well. – Michel Dec 20 '16 at 05:41
3

Here is solution:

class Person: NSObject, NSCoding {
    let name: String
    let age: Int
    required init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    required init(coder decoder: NSCoder) {
        self.name = decoder.decodeObject(forKey: "name") as? String ?? ""
        self.age = decoder.decodeInteger(forKey: "age")
    }

    func encode(with coder: NSCoder) {
        coder.encode(name, forKey: "name")
        coder.encode(age, forKey: "age")
    }
}

How to use:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let newPerson = Person(name: "Joe", age: 10)
        var people = [Person]()
        people.append(newPerson)
        let encodedData = NSKeyedArchiver.archivedData(withRootObject: people)
        UserDefaults.standard().set(encodedData, forKey: "people")
        if let data = UserDefaults.standard().data(forKey: "people"),
            myPeopleList = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Person] {
            myPeopleList.forEach({print( $0.name, $0.age)})  // Joe 10
        } else {
            print("There is an issue")
        }
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

Reference: Swift 3 saving and retrieving custom object from userDefaults

pkamb
  • 33,281
  • 23
  • 160
  • 191
Amit Soni
  • 41
  • 2
1

Swift 3:

I adopt the double question mark (??) method and it work like a charm in only one line.

If val is not nil than it is unwrapped and the value returned. If it is nil then aDecoder.decodeInteger(forKey: "val") returned:

self.val = aDecoder.decodeObject(forKey: "val") as? Int ?? aDecoder.decodeInteger(forKey: "val")
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133