Недетерминированное поведение при использовании unarchiveObjectWithData в Swift

У меня проблемы с приведенным ниже кодом в Swift. Я извлек код из одного из своих приложений и настроил игровую площадку, чтобы проверить его поведение.

Иногда следующая строка выполняется успешно, а иногда нет:

if var selectedCards = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? [Account : Card] {

Ничего не меняется между запусками на игровой площадке.

Если бы кто-нибудь мог мне помочь, это было бы очень признательно. Это сводит меня с ума!

Когда линия выходит из строя на игровой площадке, она завершается ошибкой ниже:

Выполнение было прервано, причина: EXC_BAD_INSTRUCTION (код=EXC_I386_INVOP, субкод=0x0

Когда строка терпит неудачу в моем приложении, она завершается с той же ошибкой, и это печатается в консоли отладки:

фатальная ошибка: неожиданно найдено nil при развертывании необязательного значения

Игровая площадка

import Foundation

Класс аккаунта

class Account: NSObject, Comparable, NSCoding, NSCopying {

    var email: String = ""
    var locked: Bool = false

    override var hashValue: Int {
        return email.hash
    }

    required init(email: String) {
        super.init()
        self.email = email
        self.locked = false
    }

    required init?(coder decoder: NSCoder) {
        email = decoder.decodeObjectForKey("email") as! String
        locked = decoder.decodeBoolForKey("locked")
    }

    func encodeWithCoder(coder: NSCoder) {
        coder.encodeObject(email, forKey: "email")
        coder.encodeBool(locked, forKey: "locked")
    }

    func copyWithZone(zone: NSZone) -> AnyObject {
        let account = self.dynamicType.init(email: self.email)
        account.email = self.email
        account.locked = self.locked
        return account
    }

    override func isEqual(object: AnyObject?) -> Bool {
        guard let account = object as? Account else { return false }
        return account == self
    }
}

func ==(lhs: Account, rhs: Account) -> Bool {
    let emailEqual = lhs.email == rhs.email
    return emailEqual
}

func <(lhs: Account, rhs: Account) -> Bool {
    return lhs.email < rhs.email
}

Класс карты

class Card: NSObject, Comparable, NSCoding {

    var id: String = ""
    var name: String = ""

    override var hashValue: Int {
        return id.hash
    }

    init(id: String, name: String) {
        super.init()
        self.id = id
        self.name = name
    }

    required init?(coder decoder: NSCoder) {
        id = decoder.decodeObjectForKey("id") as! String
        name = decoder.decodeObjectForKey("name") as! String
    }

    func encodeWithCoder(coder: NSCoder) {
        coder.encodeObject(id, forKey: "id")
        coder.encodeObject(name, forKey: "name")
    }

    override func isEqual(object: AnyObject?) -> Bool {
        guard let card = object as? Card else { return false }
        return card == self
    }
}

func ==(lhs: Card, rhs: Card) -> Bool {
    let idEqual = lhs.id == rhs.id
    let nameEqual = lhs.name == rhs.name
    return idEqual && nameEqual
}

func <(lhs: Card, rhs: Card) -> Bool {
    if lhs.name == rhs.name {
        return lhs.id < rhs.id
    }
    return lhs.name < rhs.name
}

Тест

let account = Account(email: "[email protected]")
let card = Card(id: "1", name: "Card 1")

let map: [Account : Card] = [account : card]

let data = NSKeyedArchiver.archivedDataWithRootObject(map)

if var selectedCards = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? [Account : Card] {
    let selectedCard = Card(id: "2", name: "Card 2")
    selectedCards[account] = selectedCard

    print("Success")
}

person Gary Grace    schedule 15.04.2016    source источник
comment
... as? [Account: Card]: взгляните на требуемые неудачные инициализаторы для этих двух классов, например. принудительное преобразование типов email = ... as! String и id = ... as! String. Они не дадут сбой и не вернут nil в случае, если декодер (например, для ключа "id") выдаст nil до принудительного приведения (as), а, скорее, произойдет сбой. Попробуйте переписать эти инициализаторы, используя безопасную распаковку декодирования объекта (например, необязательную привязку), возвращая nil в случае сбоя декодирования (следовательно, сбой инициализации, а не ее сбой).   -  person dfrib    schedule 15.04.2016
comment
Подозреваю, что мне просто нужно добавить @objc(Account) перед классом Account и @objc(Card) перед классом Card, поэтому используются неискаженные имена классов   -  person Gary Grace    schedule 15.04.2016
comment
Спасибо дфри. Определенно стоит сделать. Однако он должен работать, и это не объясняет, почему иногда он работает, а иногда нет, когда данные одинаковы.   -  person Gary Grace    schedule 15.04.2016
comment
NSKeyedArchiver здесь не имеет значения, так как мы можем свести ту же проблему к явной попытке создать NSDictionary как [account: card], то есть let map: NSDictionary = [account : card] (или просто [account: "foo"]). Это также время от времени дает сбой с фатальной ошибкой неожиданно найдено nil при развертывании необязательного значения. Естественно, NSDictionary не может содержать ключи, равные nil, но я не могу объяснить, почему account недетерминированно дает nil при вводе в качестве ключа/значения в NSDictionary. Этот вопрос и ответ кажется связанным.   -  person dfrib    schedule 15.04.2016