NSKeyedUnarchiver.decodeObject
provoquera un crash/SIGABRT
si la classe d'origine est inconnue. La seule solution que j'ai trouvée pour résoudre ce problème remonte aux débuts de Swift et nécessitait l'utilisation de l'objectif C (également antérieure à la mise en œuvre par Swift 2 de guard
, throws
, try
& catch
). Je pourrais trouver la voie de l’objectif C, mais je préférerais, si possible, comprendre une solution exclusivement Swift.
Par exemple, les données ont été codées avec NSPropertyListFormat.XMLFormat_v1_0
. Le code suivant échouera à unarchiver.decodeObject()
si la classe des données codées est inconnue.
//...
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
}
//...
Je suis à la recherche d'un seul moyen pour Swift 2 de vérifier si les données sont valides avant de tenter .decodeObject
- puisque .decodeObject
n'a pas de variable throws
- ce qui signifie que try
- catch
ne semble pas être une option dans Swift (les méthodes sans throws
ne peuvent pas être encapsulées AFAIK) . Ou bien, une autre façon de décoder les données, ce qui jettera une erreur que je peux détecter si le décodage échoue. Je veux que l'utilisateur puisse importer un fichier à partir d'un lecteur iCloud ou de Dropbox - il doit donc être correctement validé. Je ne peux pas supposer que les données encodées sont en sécurité.
Les méthodes NSKeyedUnarchiver
.unarchiveTopLevelObjectWithData
& .validateValue
ont toutes les deux throws
. Existe-t-il un moyen de les utiliser? Je n'arrive pas à comprendre comment même commencer à essayer d'implémenter validateValue
dans ce contexte. Est-ce même un itinéraire possible? Ou devrais-je me tourner vers l'une des autres méthodes pour trouver une solution?
Ou est-ce que quelqu'un connaît un autre moyen alternatif pour Swift 2 de résoudre ce problème? Je crois que la clé qui m'intéresse est probablement intitulée $classname
- mais TBH ne me permet pas d'essayer de trouver comment mettre en œuvre validateValue
- ou même si ce serait le bon moyen de persévérer. . J'ai l'impression qu'il me manque quelque chose d'évident.
EDIT: Voici une solution - grâce à l'excellente réponse de rintaro ci-dessous
La réponse initiale a résolu le problème pour moi - c’est-à-dire la mise en place d’un délégué.
Pour l'instant cependant, je suis parti avec une solution construite autour de la réponse modifiée supplémentaire de rintaro comme suit:
//...
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
}
//...
Lorsque NSKeyedUnarchiver
rencontre des classes inconnues, la méthode unarchiver(_:cannotDecodeObjectOfClassName:originalClasses:)
delegate est appelée.
Le délégué peut, par exemple, charger du code pour introduire la classe dans le moteur d'exécution et la retourner, ou substituer un autre objet de classe. Si le délégué renvoie
nil
, la désarchivage est abandonnée et la méthode lève uneNSInvalidUnarchiveOperationException
.
Donc, vous pouvez implémenter le délégué comme ceci:
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
}
}
Ensuite:
let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat)
let delegate = MyUnArchiverDelegate()
unarchiver.delegate = delegate
unarchiver.decodeObjectForKey("root")
// -> `nil` if the root object is unknown class.
AJOUTÉE:
Je n'ai pas remarqué que NSCoder
a extension
avec des méthodes plus rapides:
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?
}
Vous pouvez:
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}
une autre solution consiste à corriger le nom de la classe utilisée pour NSCoding. Vous devez simplement utiliser:
NSKeyedArchiver.setClassName("List", forClass: List.self
avant la sérialisationNSKeyedUnarchiver.setClass(List.self, forClassName: "List")
avant de désérialiserpartout où cela est nécessaire.
On dirait que les extensions iOS préfixent le nom de la classe avec le nom de l'extension.
En fait, c’est la raison pour laquelle nous devrions creuser profondément. Il y a une possibilité, vous créez un chemin d'archive nommé xxx.archive, puis vous désarchivez du chemin (xxx.archive), maintenant tout va bien. Mais si vous changez le nom de la cible, quand vous désarchivez, le crash est survenu !!! C’est parce que archive & désarchiver les différents objets (la vérité est que nous archivons & désarchivons target.obj, pas seulement obj) . Le moyen le plus simple est de supprimer le chemin d’archive ou d’utiliser un autre chemin d’archive. Et ensuite, nous devrions réfléchir à la manière d'éviter le crash, try-catch est notre assistant mentionné par rintaro .
J'avais le même problème. Ajouter @objc à la déclaration de classe a fonctionné pour moi.
@objc(YourClass)
class YourClassName: NSObject {
}