Невозможно декодировать объект класса
Я пытаюсь отправить "класс" в расширение "Watchkit", но получаю эту ошибку.
* Завершение приложения из-за неотображенного исключения "NSInvalidUnarchiveOperationException", причина: "*- [NSKeyedUnarchiver decodeObjectForKey:]: не может декодировать объект класса (MyApp.Person)
Архивирование и разблокировка отлично работают в приложении iOS, но не сообщаются с расширением watchkit. Что не так?
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")
}
}
Ответы
Ответ 1
ПРИМЕЧАНИЕ.. Хотя информация в этом ответе верна, лучшим ответом является тот, который указан ниже @agy.
Это вызвано созданием компилятора MyApp.Person
и MyAppWatchKitExtension.Person
из того же класса. Обычно это происходит из-за совместного использования одного и того же класса по двум целям вместо создания структуры для его совместного использования.
Два исправления:
Правильное исправление заключается в извлечении Person
в фреймворк. Как основное приложение, так и расширение watchkit должны использовать фреймворк и будут использовать тот же класс *.Person
.
Обходной путь заключается в сериализации вашего класса в объект Foundation (например, NSDictionary
), прежде чем вы его сохраните и передадите. NSDictionary
будет кодироваться и декодироваться как для приложения, так и для расширения. Хороший способ сделать это - реализовать протокол RawRepresentable
на Person
.
Ответ 2
В соответствии с Взаимодействие с API Objective-C:
Когда вы используете атрибут @objc(
name )
в классе Swift, класс становится доступным в Objective-C без какого-либо пространства имен. В результате этот атрибут также может быть полезен при переносе архивируемого класса Objective-C в Swift. Поскольку архивные объекты хранят имя своего класса в архиве, вы должны использовать атрибут @objc(
name )
, чтобы указать то же имя, что и ваш класс Objective-C, чтобы старые архивы могли быть распакованы вашим новым классом Swift.
Добавив аннотацию @objc(name)
, пространство имен игнорируется, даже если мы просто работаем с Swift. Давайте продемонстрируем. Представьте, что цель A
определяет три класса:
@objc(Adam)
class Adam:NSObject {
}
@objc class Bob:NSObject {
}
class Carol:NSObject {
}
Если цель B вызывает эти классы:
print("\(Adam().classForCoder)")
print("\(Bob().classForCoder)")
print("\(Carol().classForCoder)")
Выход будет:
Adam
B.Bob
B.Carol
Однако, если цель A вызывает эти классы, результат будет:
Adam
A.Bob
A.Carol
Чтобы решить проблему, просто добавьте директиву @objc (name):
@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")
}
}
Ответ 3
Мне пришлось добавить следующие строки после настройки рамки, чтобы сделать NSKeyedUnarchiver
корректной.
Перед распаковкой:
NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName")
Перед архивированием:
NSKeyedArchiver.setClassName("YourClassName", forClass: YourClassName.self)
Ответ 4
У меня была аналогичная ситуация, когда мое приложение использовало мою инфраструктуру Core
, в которой я сохранил все классы моделей. Например. Я сохранял и извлекал объект UserProfile
с помощью NSKeyedArchiver
и NSKeyedUnarchiver
, когда я решил переместить все мои классы в MyApp
NSKeyedUnarchiver
, запуская ошибки, потому что сохраненные объекты были как Core.UserProfile
, а не MyApp.UserProfile
как ожидаемый от unarchiver. Как я решил, это создать подкласс NSKeyedUnarchiver
и переопределить функцию 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)
}
}
Затем добавили @objc(name)
в классы, которые необходимо было заархивировать, как это предлагается в одном из ответов здесь.
И назовите его следующим образом:
if let unarchivedObject = SKKeyedUnarchiver.unarchiveObject(withFile: UserProfileServiceImplementation.archiveURL.path) as? UserProfile {
currentUserProfile = unarchivedObject
}
Это сработало очень хорошо.
Причина, по которой решение NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName")
не для меня, потому что оно не работает для вложенных объектов, например, когда UserProfile
имеет var address: Address
. Unarchiver преуспеет с UserProfile
, но будет терпеть неудачу, когда уровень будет ниже уровня Address
.
И причина, по которой решение @objc(name)
не сделало этого для меня, было потому, что я не перешел с OBJ-C на Swift, поэтому проблема была не UserProfile
→ MyApp.UserProfile
, а вместо этого Core.UserProfile
→ MyApp.UserProfile
.