Reputation: 369
I am having a problem with our approach to data persistence in our app. It was decided to use NSUserDefaults with NSCoding compliant data model, which I disagree with due to the scale of our app.
The problem I'm seeing is when the data model changes, any attempt to deserialized results in a crash. The app must be uninstalled and reinstalled in order to re-serialize.
Scenario:
This is happening because the data had been serialized with a different model than it is now attempting to be deserialized as.
Example:
class Contact: NSCoding {
var name
var address
var userId
}
... // NSCoding compliance happens next. This object gets serialized.
Someone decides that Contact needs more stuff:
class Contact: NSCoding {
var name
var address
var userId
var phoneNumber
var emailAddress
}
Attempting to deserialize the Contact object, even though the NSCoding compliance for encoding and decoding has been updated to load and deserialize, causes
fatal error: unexpectedly found nil while unwrapping an Optional value
CoreDataManager.unarchiveUser
Worker.init
So, my question is, how could we possibly avoid this crash from occurring when running an updated version of the app that has a different schema??
Upvotes: 0
Views: 365
Reputation: 70976
You're crashing because,
decodeObject(forKey:)
on a key that doesn't exist (because it didn't exist on the class when the object was encoded). This method returns nil
.!
to force-unwrap the result of #1.As a rule of thumb, if your Swift code crashes on a line that contains a !
, there's about a 95% chance that the !
is the direct cause of the crash. If the error message mentions unwrapping an optional, it's a 100% chance.
The documentation for the decodeObject(forKey:)
method explains that it may return nil
. In your case this is guaranteed to happen if you're upgrading from a previous version of the class and you're decoding a key that you just added.
Your code needs to recognize that there might not be a value for the new properties. The simplest fix is replacing as!
with as?
. Then you'll get a nil
value for the new property. For properties that are not optional, you can add something like ?? "default value"
to the end.
You could also use the containsValue(forKey:)
method to check if a value exists before trying to decode the key.
Upvotes: 4