Доступ к пользовательскому объекту в общем контейнере в Swift

Я делаю пользовательскую клавиатуру и хочу создать актив (в частности, словарь trie) в приложении-контейнере, а затем получить к нему доступ при запуске расширения клавиатуры через общий контейнер. Чтобы работать над этим, я начинаю с этого довольно простого класса:

class Person: NSObject, NSCoding {
    let name: String
    let age: Int
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    required init(coder decoder: NSCoder) {
        self.name = decoder.decodeObject(forKey: "name") as? String ?? ""
        self.age = decoder.decodeInteger(forKey: "age")
    }

    func encode(with coder: NSCoder) {
        coder.encode(name, forKey: "name")
        coder.encode(age, forKey: "age")
    }
}

Отлично. У меня настроен общий контейнер, и в функции viewDidLoad ViewController для приложения-контейнера я загружаю общий контейнер как таковой (несколько тестовых переменных и пользовательский объект):

    let shared = UserDefaults(suiteName: "group.test_keyboard")
    shared!.set("test", forKey:"key_string")
    shared!.set(false, forKey:"key_boolean")
    shared!.set(34, forKey:"key_int")

    let newPerson = Person(name: "Joe", age: 10)
    let encodedData = NSKeyedArchiver.archivedData(withRootObject: newPerson)
    shared!.set(encodedData, forKey: "people")

Чтобы проверить это, я добавил следующий код в viewWillAppear:

    let shared = UserDefaults(suiteName: "group.test_keyboard")
    let test_string = shared?.string(forKey: "key_string")

    if(test_string != nil){
        print("string-\(test_string!)")
    }
    else{
        print("nil")
    }

    // retrieving a value for a key
    if let data = shared?.data(forKey: "people"){
        let test_person = NSKeyedUnarchiver.unarchiveObject(with: data) as? Person
        print("-\(test_person!.name)")  // Joe

        let data_size = String(data.count)
        print("-\(data_size)")

    } else {
        print("There is an issue")
    }

Все нормально проверяется. Но когда я захожу в KeyboardViewController и пытаюсь получить доступ к вещам, извлечение простой строки работает нормально:

    let shared = UserDefaults(suiteName: "group.test_keyboard")
    shared?.synchronize()
    let test_string = shared?.string(forKey: "key_string")

    if(test_string != nil){
        (textDocumentProxy as UIKeyInput).insertText(test_string!)
    }
    else{
        (textDocumentProxy as UIKeyInput).insertText("nil")
    }

Но тогда, если я попытаюсь получить доступ к пользовательскому объекту Person, как таковому:

    if let data = shared?.data(forKey: "people") {
        (textDocumentProxy as UIKeyInput).insertText("found")

        (textDocumentProxy as UIKeyInput).insertText(String(data.count))

        let myPeople = NSKeyedUnarchiver.unarchiveObject(with: data) as? Person

    }

Я получаю сбой со следующим ответом об ошибке:

2017-05-10 14:34:46.938253-0700 test_keyboard[3196:857207] viewServiceDidTerminateWithError:: Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "(null)" UserInfo={Message=Service Connection Interrupted}

Данные есть, если я сделаю простую проверку data.count с обеих сторон, они будут одинакового размера (269). Если я закомментирую строку NSKeyedUnarchiver, проблем не будет, поэтому я уверен, что проблема именно в этом. Что происходит, что приводит к сбою внутри расширения клавиатуры, когда оно прекрасно работает в приложении-контейнере?


person asetniop    schedule 10.05.2017    source источник


Ответы (1)


Скорее всего дело в обкатке разных модулей. Ваше приложение — это другой модуль, чем расширение, и полное имя класса включает имя модуля. В приложении это что-то вроде AppName.Person, а в расширении что-то вроде KeyboardExt.Person. NSCoding не может сравниться с ними, поэтому его тошнит.

Есть несколько обходных путей. Один из них — создать структуру, которая используется как приложением, так и расширением, и поместить туда общий класс. Тогда имя с модулем всегда будет что-то вроде «FrameworkName.Person», и все будет работать.

Другой — указать процессу (не)архивирования, какое имя использовать для класса. Затем он напишет указанное вами имя и сопоставит его с соответствующим классом. Прежде чем записывать какие-либо данные, сделайте что-то вроде

NSKeyedArchiver.setClassName("Person", for: Person.self)

Затем, прежде чем читать какие-либо данные, сделайте наоборот:

NSKeyedUnarchiver.setClass(Person.self, forClassName: "Person")
person Tom Harrington    schedule 11.05.2017
comment
Это выглядит очень многообещающе - я попробую! - person asetniop; 11.05.2017
comment
@asetniop Вы связались со мной через мой веб-сайт и через Twitter, чтобы спросить, отвечу ли я на это. Если это помогло, пожалуйста, отметьте это как принятое. Если нет, опишите, пожалуйста, какие проблемы у вас были с ним. - person Tom Harrington; 15.05.2017
comment
Это произошло! (Извините, что медленно - моей жене понадобился макбук для депонирования) Большое спасибо! - person asetniop; 16.05.2017