Быстрый способ только для предотвращения сбоя 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
}
//...
Ответы
Ответ 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 префиксное имя класса с расширением.