ios - tesis - No se puede decodificar objeto de clase
tesis sobre youtube pdf (4)
Estoy intentando enviar una "Clase" a mi extensión de Watchkit, pero recibo este error.
* Aplicación de terminación debido a la excepción no detectada ''NSInvalidUnarchiveOperationException'', razón: ''* - [NSKeyedUnarchiver decodeObjectForKey:]: no se puede descodificar objeto de la clase (MyApp.Person)
Archivar y desarchivar funciona bien en la aplicación iOS, pero no mientras se comunica con la extensión watchkit. Que pasa
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
}
Cambio de persona
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")
}
}
De acuerdo con la interacción con las API de Objective-C :
Cuando usa el
@objc(
nombre)
en una clase Swift, la clase está disponible en Objective-C sin ningún espacio de nombre. Como resultado, este atributo también puede ser útil cuando migra una clase de Objective-C archivable a Swift. Debido a que los objetos archivados almacenan el nombre de su clase en el archivo, debe usar el@objc(
nombre)
para especificar el mismo nombre que su clase de Objective-C para que su nueva clase Swift no pueda archivar los archivos más antiguos.
Al agregar la anotación @objc(name)
, el espacio de nombres se ignora incluso si solo estamos trabajando con Swift. Vamos a demostrar. Imagina que el objetivo A
define tres clases:
@objc(Adam)
class Adam:NSObject {
}
@objc class Bob:NSObject {
}
class Carol:NSObject {
}
Si el objetivo B llama a estas clases:
print("/(Adam().classForCoder)")
print("/(Bob().classForCoder)")
print("/(Carol().classForCoder)")
La salida será:
Adam
B.Bob
B.Carol
Sin embargo, si el objetivo A llama a estas clases, el resultado será:
Adam
A.Bob
A.Carol
Para resolver su problema, solo agregue la directiva @objc (nombre):
@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")
}
}
Tuve que agregar las siguientes líneas después de configurar el marco para hacer que el NSKeyedUnarchiver
funcione correctamente.
Antes de desarchivar:
NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName")
Antes de archivar:
NSKeyedArchiver.setClassName("YourClassName", forClass: YourClassName.self)
Tuve una situación similar en la que mi aplicación usó mi marco Core
en el que conservé todas las clases modelo. Por ejemplo, NSKeyedArchiver
y NSKeyedUnarchiver
objeto UserProfile
usando NSKeyedArchiver
y NSKeyedUnarchiver
, cuando decidí mover todas mis clases a MyApp
NSKeyedUnarchiver
comenzó a lanzar errores porque los objetos almacenados eran como Core.UserProfile
y no MyApp.UserProfile
como esperaba el desarchivador. Cómo lo resolví fue crear una subclase de NSKeyedUnarchiver
y anular la función 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)
}
}
Luego agregó @objc(name)
a las clases que necesitaban archivarse, como se sugiere en una de las respuestas aquí.
Y llámalo así:
if let unarchivedObject = SKKeyedUnarchiver.unarchiveObject(withFile: UserProfileServiceImplementation.archiveURL.path) as? UserProfile {
currentUserProfile = unarchivedObject
}
Funcionó muy bien.
La razón por la que la solución NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName")
no fue para mí porque no funciona para objetos anidados, como cuando UserProfile
tiene una var address: Address
. Unarchiver tendrá éxito con el UserProfile
pero fallará cuando llegue a un nivel más profundo de Address
.
Y la razón por la que la @objc(name)
solo no lo hizo por mí es porque no me moví de OBJ-C a Swift, por lo que el problema no era UserProfile
-> MyApp.UserProfile
sino Core.UserProfile
- > MyApp.UserProfile
.
NOTA: Si bien la información de esta respuesta es correcta, la mejor forma de responder es la siguiente: @agy.
Esto se debe a que el compilador está creando MyApp.Person
& MyAppWatchKitExtension.Person
de la misma clase. Generalmente se produce al compartir la misma clase en dos objetivos en lugar de crear un marco para compartirlo.
Dos correcciones:
La solución adecuada es extraer Person
en un marco. Tanto la aplicación principal como la extensión de watchkit deben usar el marco y utilizarán la misma clase *.Person
.
La solución consiste en serializar su clase en un objeto Foundation (como NSDictionary
) antes de guardarlo y pasarlo. El NSDictionary
será codificado y decodificable tanto en la aplicación como en la extensión. Una buena manera de hacer esto es implementar el protocolo RawRepresentable
en Person
.