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.
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")
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
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
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")
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With