【问题标题】:NSKeyedUnarchiver crashes swift 3 on the second runNSKeyedUnarchiver 在第二次运行时崩溃了 swift 3
【发布时间】:2017-09-12 17:55:51
【问题描述】:

我正在尝试将自定义对象保存到 UserDefaults,并且我正在使用 this 作为源代码。 它在获取部分立即崩溃。这是我的代码:

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")
        }
    }
}

不确定我是否遗漏了什么,字符串保存和检索成功,但不是这个自定义对象。

像这样稍微重写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 的第二次运行时发生,因此第一次运行 - 它有效,并尝试再次运行 - 与描述的输出一起崩溃。那么对象的存储一定有什么东西?

【问题讨论】:

  • 不存在崩溃。您从崩溃中得到什么消息?崩溃日志告诉你什么?
  • 另外,您的代码没有任何意义。始终提供足够的代码以便于理解。您参考了self.defaults,但我们无法知道这到底是什么。展示下。显示重现问题所需的一切。
  • 最好发布至少 10 行的崩溃日志
  • 你能告诉我们你是如何使用那段代码的吗?我使用了那段代码,它根本没有崩溃
  • 异常信息是什么?没有它,这个故障转储告诉我们除了存档无法解码之外几乎没有什么信息。它可能已损坏,您可能在没有正确将类列入白名单的情况下遵守 NSSecureCoding,您可能正在解码不再存在的类,等等。

标签: ios swift swift3 nskeyedarchiver nskeyedunarchiver


【解决方案1】:

根据https://stackoverflow.com/a/4197319/4601900 问题是不允许将自定义类作为节点存储在设置 plist (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)

希望对你有帮助

【讨论】:

    【解决方案2】:

    我在 Swift Playground 上进行了本地尝试,效果很好。什么都不会崩溃。但是 Xcode 建议我在 VIP 类名之前添加 @objc。请确保我们正确地将值设置为 VIPs 变量。

    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: "dev.rnarendra@gmail.com", name: "naren", relation: "cool")
    
    Settings.VIPs = [vip]
    _ = Settings.VIPs
    

    【讨论】:

      【解决方案3】:

      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
      
      ....
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-09-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多