Невозможно декодировать объект класса


Я пытаюсь отправить "класс" в расширение "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, поэтому проблема была не UserProfileMyApp.UserProfile, а вместо этого Core.UserProfileMyApp.UserProfile.