【问题标题】:NSKeyUnarchiver causing CrashNSKeyedUnarchiver 导致崩溃
【发布时间】:2017-01-20 18:15:08
【问题描述】:

斯威夫特 3,

我在我的 iOS 应用程序中使用 NSUserDefaults 来保存和加载 indexPath 以及发生操作的行的哪个部分。因为我的表格视图中的每一行都有一个按钮。在 ViewDidLoad 中重新加载表格时再次加载。

在 ViewDidLoad 中,我正在调用应该保存和加载任何内容的 fetch 函数。

func fetchData() {

    // request from remote or local
    data = [testArray]

    // Update the items to first section has 0 elements,
    // and place all data in section 1
    items = [[], data ?? []]

    // apply ordering
    applySorting() { "\($0)" }

    // save ordering
    saveSorting() { "\($0)" }

    // refresh the table view
    myTableView.reloadData()
}

在我的 buttonAction 中,我使用了 saveSorting() 函数。

func saveSorting(_ dataIdBlock: (Any) -> String) {

    guard let items = self.items else { return }

    for (section, rows) in items.enumerated() {
        for (row, item) in rows.enumerated() {
            let indexPath = IndexPath(row: row, section: section)
            let dataId = dataIdBlock(item)
            let ordering = DataHandling(dataId: dataId, indexPath: indexPath)
            ordering.save(defaults: indexPath.defaultsKey)
        }
    }
}

这是我的断点图片,显示了日志以及它在代码中中断的位置。

感谢您在修复此崩溃方面提供的帮助。该应用程序甚至不加载它在应用程序完全加载之前停留在白屏。谢谢。

这里是代码

class DataHandling: NSObject, NSCoding {

var indexPath: IndexPath?
var dataId: String?

init(dataId: String, indexPath: IndexPath) {
    super.init()
    self.dataId = dataId
    self.indexPath = indexPath
}

required init(coder aDecoder: NSCoder) {

    if let dataId = aDecoder.decodeObject(forKey: "dataId") as? String {
        self.dataId = dataId
    }

    if let indexPath = aDecoder.decodeObject(forKey: "indexPath") as? IndexPath {
        self.indexPath = indexPath
    }

}

func encode(with aCoder: NSCoder) {
    aCoder.encode(dataId, forKey: "dataId")
    aCoder.encode(indexPath, forKey: "indexPath")
}

func save(defaults box: String) -> Bool {

    let defaults = UserDefaults.standard
    let savedData = NSKeyedArchiver.archivedData(withRootObject: self)
    defaults.set(savedData, forKey: box)
    return defaults.synchronize()

}

convenience init?(defaults box: String) {

    let defaults = UserDefaults.standard
    if let data = defaults.object(forKey: box) as? Data,
        let obj = NSKeyedUnarchiver.unarchiveObject(with: data) as? DataHandling,
        let dataId = obj.dataId,
        let indexPath = obj.indexPath {
        self.init(dataId: dataId, indexPath: indexPath)
    } else {
        return nil
    }

}

class func allSavedOrdering(_ maxRows: Int) -> [Int: [DataHandling]] {

    var result: [Int: [DataHandling]] = [:]
    for section in 0...1 {
        var rows: [DataHandling] = []
        for row in 0..<maxRows {
            let indexPath = IndexPath(row: row, section: section)
            if let ordering = DataHandling(defaults: indexPath.defaultsKey) {
                rows.append(ordering)
            }
            rows.sort(by: { $0.indexPath! < $1.indexPath! })
        }
        result[section] = rows
    }

    return result

 }

}

错误代码:

*** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (CustomCellSwift.DataOrdering) for key (root); the class may be defined in source code or a library that is not linked'

JSON 代码

func retrieveData() {

    let getDataURL = "http://ip/test.org/Get.php"
    let url: NSURL = NSURL(string: getDataURL)!

    do {

        let data: Data = try Data(contentsOf: url as URL)
        jsonArray = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! NSMutableArray

        // Looping through jsonArray
        for i in 0..<jsonArray.count {

            // Create Test Object
            let gID: String = (jsonArray[i] as AnyObject).object(forKey: "id") as! String
            let gName: String = (jsonArray[i] as AnyObject).object(forKey: "gameName") as! String

            // Add Test Objects to Test Array
            testArray.append(Test(gameTest: tName, andTestID: tID))

        }
    }
    catch {
        print("Error: (Getting Data)")
    }

    myTableView.reloadData()
}

应用排序代码

func applySorting(_ dataIdBlock: (Any) -> String) {

    // get all saved ordering
    guard let data = self.data else { return }
    let ordering = DataHandling.allSavedOrdering(data.count)

    var result: [[Any]] = [[], []]

    for (section, ordering) in ordering {
        guard section <= 1 else { continue } // make sure the section is 0 or 1
        let rows = data.filter({ obj -> Bool in
            return ordering.index(where: { $0.dataId == .some(dataIdBlock(obj)) }) != nil
        })
        result[section] = rows
    }

    self.items = result
}

【问题讨论】:

  • 可以添加崩溃日志和DataHandling类吗?
  • 实际的错误信息是什么?
  • @Ryan 如何获取崩溃日志?它只是说(lldb),我上传了代码。
  • @l'L'l 它的一个断点崩溃,显示没有错误
  • 也许您的默认值中有旧数据?您是否从UserDefaults 重新开始并清理了所有内容? (因为它试图解码一个完全不同的类CustomCellSwift.DataOrdering,您尝试将其转换为DataHandling

标签: ios swift uitableview crash nskeyedarchiver


【解决方案1】:

您的indexPath.defaultsKey 实现是错误的。

我看到你做了in this question一个扩展:

请注意以下是原始问题中缺少的代码,而不是我的解决方案

extension IndexPath { 
    var defaultsKey: String { 

        return "data_ordering_\(section)_\(row)" 
    } 
}

这条线return "data_ordering_\(section)_\(row)" 是问题所在。你有第二个类DataOrdering,它与这个密钥一起存储。但你也喜欢保存DataHandling。然后你需要一个不同的密钥。

您需要唯一的密钥来存储在 UserDefaults 中。

例如,您应该将其更改为:

DataHandling(defaults: "DataHandling_"+ indexPath.defaultsKey)

更新

最好在save(defaults box: String)中加上类名

 defaults.set(savedData, forKey: "DataHandling_"+box)

还有convenience init?(defaults box: String)

if let data = defaults.object(forKey: "DataHandling_"+box) as? Data,

(你也应该在你使用的其他类(DataOrdering)中更改此部分,以使保存的密钥对于你保存的每个不同类都是唯一的

更新

如果你重命名了你的班级,那么只需重置UserDefaults 就足够了。您可以从模拟器中删除应用程序,或者在模拟器中转到此菜单时从模拟器中删除所有时间数据:iOS Simulator &gt; Reset Content and Settings。但是为了不再出现错误,您不应该在没有当前类名的情况下使用当前的IndexPath.defaultsKey

备注:

您可以一步将数组保存到 userdefaults。您不需要从 userdefaults 逐步读取每个对象。如果你保存一个数组,那么顺序是固定的(在我的例子中,这是相同的顺序)。我认为您应该将代码发布到https://codereview.stackexchange.com/ 以进一步优化您的代码

【讨论】:

  • 所以我把我的扩展改成你贴的底部代码?
  • 我的数组被 json 对象填充,这就是我使用这种方法的原因。
  • 扩展名随后在全球范围内可用。你又遇到了同样的问题。你需要一个 UserDefaults 的唯一键 - 如果你总是在类前面,那么它应该被保存
  • 是的,但要更改 JSON -> 数组,然后一步保存数组。只需一步保存您的items
  • 我认为另一个问题的答案不适合您的用例。
猜你喜欢
  • 2011-08-07
  • 1970-01-01
  • 2012-09-04
  • 1970-01-01
  • 2011-11-25
  • 2018-01-23
  • 2020-12-02
  • 2013-11-11
  • 2011-01-24
相关资源
最近更新 更多