tzer
tzer

Reputation: 280

Swift: decodeObjectForKey crashes if key doesn't exist

I'm unarchiving a Swift class with the following Swift code:

 required convenience init(coder decoder: NSCoder) {
    self.init()

    horseID = decoder.decodeIntegerForKey("horseID")
    name    = decoder.decodeObjectForKey("name") as String!

    // if the thumb key does not exist, the following line crashes
    thumb   = decoder.decodeObjectForKey("thumb") as UIImage!
}

The "thumb" class member was added later. I have an older archive file without the thumb data in it. Apple's documentation says that unarchiving a non-existing key returns a nil. This is the familiar Objective-C behavior. My Swift code crashes with error code: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).

Changing from as to as? doesn't remove the problem.

This makes it difficult to extend a data model in a new version. Am I doing something wrong? I'm new to Swift.

Upvotes: 0

Views: 1719

Answers (2)

sbooth
sbooth

Reputation: 16976

Since you indicated the assignment to thumb isn't executed I believe the problem is the line

name    = decoder.decodeObjectForKey("name") as String!

The decoder can return nil and you are forcing a conversion to String. If the decoder returns nil in this case you will get an error. I recommend an implementation like this:

required convenience init(coder decoder: NSCoder) {
    self.init()

    horseID = decoder.decodeIntegerForKey("horseID")
    name    = decoder.decodeObjectForKey("name") as? String
    thumb   = decoder.decodeObjectForKey("thumb") as? UIImage
}

to handle nil values that may be returned from the coder.

Upvotes: 2

Aleksi Sjöberg
Aleksi Sjöberg

Reputation: 1474

You're trying to force-cast it into UIImage, which will crash if decodeObjectForKey returns nil. You should replace as with as? to get an optional value, which you can then check if it contains a value.

If thumb isn't an optional, and you get a nil with decodeObjectForKey, you will get the error message EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0), the one you mentioned in comments. You cannot assign nil into something that is not an optional. You could fix this by giving it a default value, in case type casting would give you a nil:

thumb   = decoder.decodeObjectForKey("thumb") as? UIImage ?? [default value here]

Upvotes: 2

Related Questions