0

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.

Aleksi Sjöberg
  • 1,454
  • 1
  • 12
  • 34
tzer
  • 280
  • 4
  • 11

2 Answers2

2

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]
Aleksi Sjöberg
  • 1,454
  • 1
  • 12
  • 34
  • I tried that after Martin R's comment, but the result is the same - crash. – tzer Mar 06 '15 at 14:07
  • @ThomasZimmer Does it crash with the same error message and on same line? Could you provide the full error message here? – Aleksi Sjöberg Mar 06 '15 at 14:30
  • EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) is the exact error – tzer Mar 06 '15 at 14:40
  • Have you tried changing into `as?` when assigning name? Because it truly shouldn't crash if assigning thumb nil, unless thumb is not an optional. – Aleksi Sjöberg Mar 06 '15 at 14:49
  • alkku, "thumb" is an optional. Changing the line as you mentioned did not change the strange crash. – tzer Mar 06 '15 at 15:13
2

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.

sbooth
  • 16,646
  • 2
  • 55
  • 81
  • sbooth, no, the first line (where "name" comes out of the decoder) is properly executed. So the problem is in the line that assigns "thumb" the value from the decoder. Changing the "as UIImage!" into "as? UIImage" leads again to the crash. – tzer Mar 06 '15 at 15:17
  • Even so, if for some reason `decoder` doesn't contain a value for `name` I believe a similar crash would occur with a forced cast. – sbooth Mar 06 '15 at 15:40