Brandon M
Brandon M

Reputation: 369

NSUserDefaults and serialized data

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:

  1. User installs app
  2. User does stuff.
  3. Developer decides that a property should be added to one of the serialized objects and pushes an update.
  4. User installs update.
  5. App goes 'kaboom'.

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

Stack Trace

CoreDataManager.unarchiveUser

unarchiveObject

Worker.init

NSCoding

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

Answers (1)

Tom Harrington
Tom Harrington

Reputation: 70976

You're crashing because,

  1. You are attempting to 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.
  2. You are using ! 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

Related Questions