Подтвердить что ты не робот

Быстрый способ только для предотвращения сбоя NSKeyedUnarchiver.decodeObject?

NSKeyedUnarchiver.decodeObject приведет к сбою / SIGABRT, если исходный класс неизвестен. Единственное решение, которое я видел, чтобы поймать этот вопрос, относится к ранней истории Swift и требует использования Objective C (также предваряется реализацией Swift 2 guard, throws, try и catch). Я мог бы выяснить маршрут Objective C, но я предпочел бы, если возможно, понять решение Swift.

Например, данные были закодированы с помощью NSPropertyListFormat.XMLFormat_v1_0. Следующий код завершится с ошибкой unarchiver.decodeObject(), если класс закодированных данных неизвестен.

//...
let dat = NSData(contentsOfURL: url)!
let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat)

//it will crash after this if the class in the xml file is not known

if let newListCollection = (unarchiver.decodeObject()) as? List {
    return newListCollection
} else {
    return nil
}
//...

Я ищу метод Swift 2 только для проверки достоверности данных перед попыткой .decodeObject - поскольку .decodeObject не имеет throws - это означает, что try - catch не похоже опция в Swift (методы без throws не могут быть обернуты AFAIK). Или альтернативный способ декодирования данных, которые будут вызывать ошибку, я могу поймать, если декодирование завершится неудачно. Я хочу, чтобы пользователь мог импортировать файл с диска iCloud или Dropbox, поэтому он должен быть правильно проверен. Я не могу предположить, что закодированные данные безопасны.

В методах NSKeyedUnarchiver .unarchiveTopLevelObjectWithData и .validateValue есть throws. Возможно ли, что они могут быть использованы? Я не могу решить, как даже начать пытаться реализовать validateValue в этом контексте. Это даже возможный маршрут? Или я должен искать один из других методов решения?

Или кто-нибудь знает альтернативный вариант Swift 2 только для решения этой проблемы? Я считаю, что ключ, который меня интересует, вероятно, называется $classname - но TBH. Я не в своей глубине в отношении того, как пытаться определить, как реализовать validateValue, или даже если это будет правильный путь для продолжения, У меня есть ощущение, что я пропускаю что-то очевидное.


EDIT: вот решение - благодаря отличному ответу rintaro ниже

Первоначальный ответ решил проблему для меня - т.е. реализацию делегата.

В настоящее время, однако, я пошел с решением, построенным вокруг дополнительного отредактированного ответа rintaro следующим образом:

//...
let dat = NSData(contentsOfURL: url)!
let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat)

do {
    let decodedDataObject = try unarchiver.decodeTopLevelObject()
    if let newListCollection = decodedDataObject as? List {
        return newListCollection
    } else {
        return nil
    }
}
catch {
    return nil
}
//...
4b9b3361

Ответ 1

Когда NSKeyedUnarchiver встречается с неизвестными классами, вызывается метод делегирования unarchiver(_:cannotDecodeObjectOfClassName:originalClasses:).

Делегат может, например, загрузить некоторый код, чтобы ввести класс в среду выполнения и вернуть класс, или заменить другой объект класса. Если делегат возвращает nil, unarchive прерывается, а метод вызывает NSInvalidUnarchiveOperationException.

Итак, вы можете реализовать делегат следующим образом:

class MyUnArchiverDelegate: NSObject, NSKeyedUnarchiverDelegate {

    // This class is placeholder for unknown classes.
    // It will eventually be `nil` when decoded.
    final class Unknown: NSObject, NSCoding  {
        init?(coder aDecoder: NSCoder) { super.init(); return nil }
        func encodeWithCoder(aCoder: NSCoder) {}
    }

    func unarchiver(unarchiver: NSKeyedUnarchiver, cannotDecodeObjectOfClassName name: String, originalClasses classNames: [String]) -> AnyClass? {
        return Unknown.self
    }
}

Тогда:

let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat)
let delegate = MyUnArchiverDelegate()
unarchiver.delegate = delegate

unarchiver.decodeObjectForKey("root")
// -> `nil` if the root object is unknown class.

ADDED:

Я не заметил, что NSCoder имеет extension более быстрые методы:

extension NSCoder {
    @warn_unused_result
    public func decodeObjectOfClass<DecodedObjectType : NSCoding where DecodedObjectType : NSObject>(cls: DecodedObjectType.Type, forKey key: String) -> DecodedObjectType?
    @warn_unused_result
    @nonobjc public func decodeObjectOfClasses(classes: NSSet?, forKey key: String) -> AnyObject?
    @warn_unused_result
    public func decodeTopLevelObject() throws -> AnyObject?
    @warn_unused_result
    public func decodeTopLevelObjectForKey(key: String) throws -> AnyObject?
    @warn_unused_result
    public func decodeTopLevelObjectOfClass<DecodedObjectType : NSCoding where DecodedObjectType : NSObject>(cls: DecodedObjectType.Type, forKey key: String) throws -> DecodedObjectType?
    @warn_unused_result
    public func decodeTopLevelObjectOfClasses(classes: NSSet?, forKey key: String) throws -> AnyObject?
}

Вы можете:

do {
    try unarchiver.decodeTopLevelObjectForKey("root")
    // OR `unarchiver.decodeTopLevelObject()` depends on how you archived.
}
catch let (err) {
    print(err)
}
// -> emits something like:
// Error Domain=NSCocoaErrorDomain Code=4864 "*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyProject.MyClass) for key (root); the class may be defined in source code or a library that is not linked" UserInfo={NSDebugDescription=*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyProject.MyClass) for key (root); the class may be defined in source code or a library that is not linked}

Ответ 2

Другой способ - исправить имя класса, используемого для NSCoding. Вам просто нужно использовать:

  • NSKeyedArchiver.setClassName("List", forClass: List.self перед сериализацией
  • NSKeyedUnarchiver.setClass(List.self, forClassName: "List") перед десериализацией

где это необходимо.

Похоже, расширения iOS префиксное имя класса с расширением.