J'essaie d'envoyer une "classe" à mon extension Watchkit mais j'obtiens cette erreur.
* Arrêt de l'application en raison d'une exception non interceptée 'NSInvalidUnarchiveOperationException', raison: '* - [NSKeyedUnarchiver decodeObjectForKey:]: ne peut pas décoder l'objet de la classe (MyApp.Person)
L'archivage et le désarchivage fonctionnent correctement sur l'application iOS, mais pas lors de la communication avec l'extension watchkit. Qu'est-ce qui ne va pas?
InterfaceController.Swift
let userInfo = ["method":"getData"]
WKInterfaceController.openParentApplication(userInfo,
reply: { (userInfo:[NSObject : AnyObject]!, error: NSError!) -> Void in
println(userInfo["data"]) // prints <62706c69 7374303...
if let data = userInfo["data"] as? NSData {
if let person = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? Person {
println(person.name)
}
}
})
AppDelegate.Swift
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!,
reply: (([NSObject : AnyObject]!) -> Void)!) {
var bob = Person()
bob.name = "Bob"
bob.age = 25
reply(["data" : NSKeyedArchiver.archivedDataWithRootObject(bob)])
return
}
Person.Swift
class Person : NSObject, NSCoding {
var name: String!
var age: Int!
// MARK: NSCoding
required convenience init(coder decoder: NSCoder) {
self.init()
self.name = decoder.decodeObjectForKey("name") as! String?
self.age = decoder.decodeIntegerForKey("age")
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeInt(Int32(self.age), forKey: "age")
}
}
REMARQUE: Bien que les informations de cette réponse soient correctes, la meilleure réponse est celle ci-dessous par @agy.
Cela est dû au fait que le compilateur crée MyApp.Person
& MyAppWatchKitExtension.Person
de la même classe. Cela est généralement dû au partage de la même classe sur deux cibles au lieu de créer un cadre pour le partager.
Deux correctifs:
La bonne solution consiste à extraire Person
dans un framework. L'extension principale de l'application et du watchkit doit utiliser le framework et utilisera le même *.Person
classe.
La solution consiste à sérialiser votre classe en un objet Foundation (comme NSDictionary
) avant de l'enregistrer et de le transmettre. NSDictionary
sera codé et décodable à la fois dans l'application et dans l'extension. Une bonne façon de procéder consiste à implémenter le protocole RawRepresentable
sur Person
à la place.
Selon Interagir avec les API Objective-C :
Lorsque vous utilisez l'attribut
@objc(
)
Sur une classe Swift, la classe est disponible dans Objective-C sans aucun espace de noms. Par conséquent, cet attribut peut également être utile lorsque vous migrez une classe Objective-C archivable vers Swift. Étant donné que les objets archivés stockent le nom de leur classe dans l'archive, vous devez utilisez l'attribut@objc(
)
pour spécifier le même nom que votre classe Objective-C afin que les anciennes archives puissent être désarchivé par votre nouvelle classe Swift.
En ajoutant l'annotation @objc(name)
, l'espace de noms est ignoré même si nous travaillons simplement avec Swift. Démontrons. Imaginez que la cible A
définit trois classes:
@objc(Adam)
class Adam:NSObject {
}
@objc class Bob:NSObject {
}
class Carol:NSObject {
}
Si la cible B appelle ces classes:
print("\(Adam().classForCoder)")
print("\(Bob().classForCoder)")
print("\(Carol().classForCoder)")
La sortie sera:
Adam
B.Bob
B.Carol
Cependant, si la cible A appelle ces classes, le résultat sera:
Adam
A.Bob
A.Carol
Pour résoudre votre problème, ajoutez simplement la directive @objc (nom):
@objc(Person)
class Person : NSObject, NSCoding {
var name: String!
var age: Int!
// MARK: NSCoding
required convenience init(coder decoder: NSCoder) {
self.init()
self.name = decoder.decodeObjectForKey("name") as! String?
self.age = decoder.decodeIntegerForKey("age")
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeInt(Int32(self.age), forKey: "age")
}
}
J'ai dû ajouter les lignes suivantes après avoir configuré le framework pour que le NSKeyedUnarchiver
fonctionne correctement.
Avant de désarchiver:
NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName")
Avant l'archivage:
NSKeyedArchiver.setClassName("YourClassName", forClass: YourClassName.self)
J'ai eu une situation similaire où mon application a utilisé mon framework Core
dans lequel j'ai conservé toutes les classes de modèle. Par exemple. J'ai stocké et récupéré l'objet UserProfile
en utilisant NSKeyedArchiver
et NSKeyedUnarchiver
, lorsque j'ai décidé de déplacer toutes mes classes vers MyApp
NSKeyedUnarchiver
j'ai commencé à lancer des erreurs car les objets stockés étaient comme Core.UserProfile
et non MyApp.UserProfile
comme prévu par le désarchiveur. Comment je l'ai résolu était de créer une sous-classe de NSKeyedUnarchiver
et de remplacer la fonction classforClassName
:
class SKKeyedUnarchiver: NSKeyedUnarchiver {
override open func `class`(forClassName codedName: String) -> Swift.AnyClass? {
let lagacyModuleString = "Core."
if let range = codedName.range(of: lagacyModuleString), range.lowerBound.encodedOffset == 0 {
return NSClassFromString(codedName.replacingOccurrences(of: lagacyModuleString, with: ""))
}
return NSClassFromString(codedName)
}
}
Ensuite, nous avons ajouté @objc(name)
aux classes qui devaient être archivées, comme suggéré dans l'une des réponses ici.
Et appelez-le comme ceci:
if let unarchivedObject = SKKeyedUnarchiver.unarchiveObject(withFile: UserProfileServiceImplementation.archiveURL.path) as? UserProfile {
currentUserProfile = unarchivedObject
}
Cela a très bien fonctionné.
La raison pour laquelle la solution NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName")
n'était pas pour moi car elle ne fonctionne pas pour les objets imbriqués comme lorsque UserProfile
a un var address: Address
. Unarchiver réussira avec le UserProfile
mais échouera quand il va un niveau plus profond à Address
.
Et la raison pour laquelle la solution @objc(name)
seule ne l'a pas fait pour moi est que je ne suis pas passé de OBJ-C à Swift, donc le problème n'était pas UserProfile
-> MyApp.UserProfile
Mais à la place Core.UserProfile
-> MyApp.UserProfile
.