NSKeyedUnarchiver аварийно завершает работу Swift 3 при втором запуске

Я пытаюсь сохранить пользовательский объект в UserDefaults и использую это в качестве исходного кода. Он сразу же падает на части get. Это мой код:

class Settings {

static let defaults:UserDefaults = UserDefaults.standard

///VIP object:
class VIP: NSObject, NSCoding {
    let email: String
    let name: String
    let relation: String

    init(email: String, name: String, relation: String) {
        self.email = email
        self.name = name
        self.relation = relation
    }
    required init(coder decoder: NSCoder) {
        self.email = decoder.decodeObject(forKey: "email") as! String 
        self.name = decoder.decodeObject(forKey: "name") as! String
        self.relation = decoder.decodeObject(forKey: "relation") as! String
    }

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


///access VIPs array with this
static var VIPs: [Settings.VIP] {
    get {

        ////CRASH (vips is not nil, and some amount of bytes)
        if let vips = self.defaults.data(forKey: "VIPs"), let myPeopleList = NSKeyedUnarchiver.unarchiveObject(with: vips) as? [Settings.VIP] {
            myPeopleList.forEach({print($0.email)})
                return myPeopleList
            } else {
                return []
            }
        }
    set {
        let encodedData = NSKeyedArchiver.archivedData(withRootObject: newValue)
        self.defaults.set(encodedData, forKey: "VIPs")
        }
    }
}

Не уверен, что я что-то упустил, сохранение и извлечение String завершается успешно, но не этот пользовательский объект.

Немного переписав метод get следующим образом:

get {

        if let vips = self.defaults.data(forKey: "VIPs"){
            print("counting: ", vips.count)

            if let myPeopleList = NSKeyedUnarchiver.unarchiveObject(with: vips) as? [Settings.VIP]{
                myPeopleList.forEach({print($0.email)})
            }
        }

помогает мне понять, что он падает на части NSKeyedUnarchiver.unarchiveObject(). Но причина неясна.

Лог ошибки:

Date/Time:           2017-09-12 21:00:43.4970 +0200
Launch Time:         2017-09-12 21:00:43.1623 +0200
OS Version:          iPhone OS 10.3.3 (14G5037b)
Report Version:      104

Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Triggered by Thread:  2

Application Specific Information:
abort() called

Filtered syslog:
None found

Last Exception Backtrace:
0   CoreFoundation                  0x18f0c6fe0 __exceptionPreprocess + 124
1   libobjc.A.dylib                 0x18db28538 objc_exception_throw + 56
2   Foundation                      0x18fb4ab0c -[NSCoder(Exceptions) __failWithException:] + 132
3   Foundation                      0x18fb4acc0 -[NSCoder(Exceptions) __failWithExceptionName:errorCode:format:] + 436
4   Foundation                      0x18fb16dac _decodeObjectBinary + 408
5   Foundation                      0x18fb1df10 -[NSKeyedUnarchiver _decodeArrayOfObjectsForKey:] + 1544
6   Foundation                      0x18fab384c -[NSArray(NSArray) initWithCoder:] + 216
7   Foundation                      0x18fb17430 _decodeObjectBinary + 2076
8   Foundation                      0x18fb16b68 _decodeObject + 308
9   Foundation                      0x18fb15d94 +[NSKeyedUnarchiver unarchiveObjectWithData:] + 88
10  Prep                            0x100097274 0x100014000 + 537204
11  Prep                            0x100078384 0x100014000 + 410500
12  Prep                            0x100078bd8 0x100014000 + 412632
13  Prep                            0x1000a4370 0x100014000 + 590704
14  Prep                            0x1001084ac 0x100014000 + 1000620
15  Prep                            0x10010b180 0x100014000 + 1012096
16  Prep                            0x100044d28 0x100014000 + 199976
17  TCC                             0x1912d92f8 __TCCAccessRequest_block_invoke.73 + 492
18  TCC                             0x1912dc3d4 __tccd_send_block_invoke + 340
19  libxpc.dylib                    0x18e1baf30 _xpc_connection_reply_callout + 80
20  libxpc.dylib                    0x18e1baea0 _xpc_connection_call_reply + 40
21  libdispatch.dylib               0x18df7e9a0 _dispatch_client_callout + 16
22  libdispatch.dylib               0x18df8d0d4 _dispatch_queue_override_invoke + 644
23  libdispatch.dylib               0x18df8ea50 _dispatch_root_queue_drain + 540
24  libdispatch.dylib               0x18df8e7d0 _dispatch_worker_thread3 + 124
25  libsystem_pthread.dylib         0x18e187100 _pthread_wqthread + 1096
26  libsystem_pthread.dylib         0x18e186cac start_wqthread + 4

ОБНОВЛЕНИЕ:

Это происходит только при втором запуске из xcode, поэтому при первом запуске - работает, а при повторном запуске - вылетает с описанным выводом. Значит, должно быть что-то с хранением объекта?


person Async-    schedule 12.09.2017    source источник
comment
Нет такого понятия, как просто авария. Какое сообщение вы получили о сбое? Что вам сообщает журнал сбоев?   -  person matt    schedule 12.09.2017
comment
Кроме того, ваш код не имеет смысла в его нынешнем виде. Всегда предоставляйте достаточно кода, чтобы его можно было понять. Вы ссылаетесь на self.defaults, но у нас нет возможности узнать, что это вообще такое. Покажи это. Покажите все, что нам нужно для того, чтобы воспроизвести проблему.   -  person matt    schedule 12.09.2017
comment
Лучше, если вы выложите хотя бы 10 строк крашлога   -  person Andrea    schedule 12.09.2017
comment
Не могли бы вы показать нам, как вы используете этот фрагмент кода? Я использовал этот фрагмент кода, и у меня он вообще не вылетал   -  person TNguyen    schedule 18.09.2017
comment
Что такое сообщение об исключении? Без этого этот аварийный дамп мало что говорит нам, кроме того, что архив не может быть расшифрован. Он может быть поврежден, вы можете соответствовать NSSecureCoding без надлежащего внесения класса в белый список, вы можете декодировать класс, который больше не существует, и т. д.   -  person dgatwood    schedule 19.09.2017
comment
Как сказал Нарендра Кумар Р., пожалуйста, дважды проверьте, что все элементы вашего массива VIP являются VIP-объектами. Из вашего отчета о сбое кажется, что сбой происходит, как только он пытается декодировать 1-й объект VIP. Так что, может быть, его нет.   -  person Reinhard Männer    schedule 20.09.2017


Ответы (3)


Согласно https://stackoverflow.com/a/4197319/4601900 проблема заключается в том, что хранение пользовательских классов в качестве узла в плист настроек (NSUserDefaults) не разрешен, так как данные хранятся в файловой системе, а не в объекте из приложения. Приложение настроек (где эти данные также будут видны) понятия не имеет, что такое "VIP" объект.

Я столкнулся с той же проблемой, и мне удалось решить ее, физически сохранив данные в файле и извлекая их из места, где они были сохранены.

Я сохраняю 2-3 значения динамически в соответствии с ключом

Я создал общие функции, которые были мне полезны.

let LocalServiceCacheDownloadDir        = "LocalData"
// Put your keys here
enum WSCacheKeys:String {
    case Key1 = "Test1"
    case Key2 = "Test2"
    case Key3 = "Test3"

}


func getBaseForCacheLocal(with fileName:String) -> String? {

        let filePath = FileManager.default.getDocumentPath(forItemName: self.LocalServiceCacheDownloadDir)
        if FileManager.default.directoryExists(atPath: filePath) {
            return filePath.stringByAppendingPathComponent(fileName)
        } else {
            if  FileManager.default.createDirectory(withFolderName: self.LocalServiceCacheDownloadDir) {
                return filePath.stringByAppendingPathComponent(fileName)
            }
        }
        return nil
    }



    //------------------------------------------------------------

    func cacheDataToLocal<T>(with Object:T,to key:WSCacheKeys) -> Bool {
        let success = NSKeyedArchiver.archiveRootObject(Object, toFile: getBaseForCacheLocal(with: key.rawValue)!)
        if success {
            Logger.success(s: "Local Data Cached\(String(describing: getBaseForCacheLocal(with: key.rawValue)))" as AnyObject)
        } else {
            Logger.error(s: "Sorry Failed")
        }

        return success

    }

    //------------------------------------------------------------

     func loadCachedDataFromLocal<T>(with key:WSCacheKeys ) -> [T]? {
        return NSKeyedUnarchiver.unarchiveObject(withFile: getBaseForCacheLocal(with: key.rawValue)!) as? [T]
    }


    //------------------------------------------------------------

Вы можете получить данные, такие как

    let objects:[VIP]? = DataManager.sharedManager.loadCachedDataFromLocal(with: .Key1)

Как я сохраняю, когда приходит ответ от веб-сервиса

  self.cacheDataToLocal(with: response.result.value!, to: .Key1)

Надеюсь, это будет полезно для вас

person Prashant Tukadiya    schedule 25.09.2017

Я попробовал это локально на Swift Playground, и это работает хорошо. Ничего не разбивается. Но Xcode предложил мне добавить @objc перед именем класса VIP. Пожалуйста, убедитесь, что мы правильно устанавливаем значения в переменную VIP.

class Settings {

    static let defaults:UserDefaults = UserDefaults.standard

    ///VIP object:
    @objc(_TtCC10Playground8Settings3VIP)class VIP: NSObject, NSCoding {
        let email: String
        let name: String
        let relation: String

        init(email: String, name: String, relation: String) {
            self.email = email
            self.name = name
            self.relation = relation
        }
        required init(coder decoder: NSCoder) {
            self.email = decoder.decodeObject(forKey: "email") as! String
            self.name = decoder.decodeObject(forKey: "name") as! String
            self.relation = decoder.decodeObject(forKey: "relation") as! String
        }

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


    ///access VIPs array with this
    static var VIPs: [Settings.VIP] {
        get {

            ////CRASH (vips is not nil, and some amount of bytes)
            if let vips = self.defaults.data(forKey: "VIPs"), let myPeopleList = NSKeyedUnarchiver.unarchiveObject(with: vips) as? [Settings.VIP] {
                myPeopleList.forEach({print($0.email)})
                return myPeopleList
            } else {
                return []
            }
        }
        set {
            let encodedData = NSKeyedArchiver.archivedData(withRootObject: newValue)
            self.defaults.set(encodedData, forKey: "VIPs")
        }
    }
}


let vip = Settings.VIP.init(email: "[email protected]", name: "naren", relation: "cool")

Settings.VIPs = [vip]
_ = Settings.VIPs
person Narendra Kumar R    schedule 19.09.2017

XCode 9 предлагает мне неявно установить закодированное имя для класса VIP:

введите здесь описание изображения

Попробуйте внести следующие изменения и повторить тесты:

class Settings {

    static let defaults:UserDefaults = UserDefaults.standard

    ///VIP object:
    @objc(EncodedVIP)class VIP: NSObject, NSCoding {
        let email: String
        let name: String
        let relation: String

....
person anatoliy_v    schedule 25.09.2017