在数组上使用 NSUserDefaults

Using NSUserDefaults on arrays

我正在尝试使用 NSUserDefaults 将数组保存到我的应用程序的核心数据中。我认为使用 NSUserDefaults 会很好,但问题是无论我在哪里放置创建默认值的代码,它都会引发 SIGABRT 错误。

这是创建默认值的代码:

let levelArrayDefault = NSUserDefaults.standardUserDefaults()
    levelArrayDefault.setValue(levelsArray, forKey: "levelsArray")
    levelArrayDefault.synchronize()

levelsArray 是 List 对象的数组:

    class List: NSObject, NSCoding {
    // MARK: Properties
    var name: String
    var AnswersArray = [Answer]()


    init?(name: String) {
        // Initialize stored properties.
        self.name = name

        if name.isEmpty {
            return nil
        }

    }

    required init(coder decoder: NSCoder){
        self.AnswersArray = (decoder.decodeObjectForKey("AA") as? [Answer])!
        self.name = (decoder.decodeObjectForKey("name") as? String)!
    }
    func encodeWithCoder(coder: NSCoder) {
        if let AnswersArray = AnswersArray { coder.encodeObject(AnswersArray, forKey: "AA") }
        if let name = name { coder.encodeObject(name, forKey: "name") }
    }


}
class Answer: NSObject, NSCoding {
    var EnglishAnswer: String = ""
    var ChineseAnswer: String = ""
    init(newEng: String, newChi: String){
        self.EnglishAnswer = newEng
        self.ChineseAnswer = newChi
    }
    required init(coder decoder: NSCoder){
        self.EnglishAnswer = (decoder.decodeObjectForKey("EnglishAnswer") as? String)!
        self.ChineseAnswer = (decoder.decodeObjectForKey("ChineseAnswer") as? String)!
    }
    func encodeWithCoder(coder: NSCoder) {
        if let EnglishAnswer = EnglishAnswer { coder.encodeObject(EnglishAnswer, forKey: "EnglishAnswer") }
        if let ChineseAnswer = ChineseAnswer { coder.encodeObject(ChineseAnswer, forKey: "ChineseAnswer") }
    }

}

如何阻止 SIGABRT 弹出并获取要存储的数组。 将不胜感激。

如果您想在 NSUserDefaults 中保存您的自定义对象,仅仅使您的 class NSCoding 兼容是不够的——您必须实际将数据编码为 NSData 对象。这是一个常见的错误——请参阅 了解类似情况。

因此,您已将 NSCoding 添加到 AnswerList class 中。这是一个好的开始。在继续之前,您应该通过使用 NSKeyedArchiver 将包含一些 Answer 对象的 List 对象示例编码为 NSData,然后使用 NSKeyedUnarchiver 将该数据对象解码回您的 List。验证您关心的所有内容都可以毫无问题地完成往返。这将是使用 Xcode 测试工具的绝佳场所——您可以编写一个完全按照我所描述的方式执行的单元测试。

一旦您知道 NSCoding 内容正确,您应该修改您的代码,以便它将您的 List 编码为 NSData 并将生成的数据对象存储在 NSUserDefaults 使用 -setObject:forKey: 方法。

在将其存储到 NSUserDefaults 之前,您需要使用 NSKeyedArchiver 将其转换为 NSData,这样尝试:

更新:Xcode 11.4 • Swift 5.2 或更高版本

import UIKit
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let list = List(name: "Student")
        list.answers = [Answer(english: "english answer", chinese: "中文回答")]
        let data = (try? NSKeyedArchiver.archivedData(withRootObject: [list], requiringSecureCoding: false)) ?? Data()
        UserDefaults.standard.set(data, forKey: "listData")
        guard
            let loadedData = UserDefaults.standard.data(forKey: "listData"),
            let loadedArray = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(loadedData) as? [List]
            else { return }
        print(loadedData.count)
        print(loadedArray.first ?? "none")
        print(loadedArray.first?.name ?? "no name")
        print(loadedArray.first?.answers.first?.english ?? "no english")
        print(loadedArray.first?.answers.first?.chinese ?? "no chinese")
    }
}

class Answer: NSObject, NSCoding {
    let english: String
    let chinese: String
    init(english: String, chinese: String) {
        self.english = english
        self.chinese = chinese
    }
    required init(coder decoder: NSCoder) {
        self.english = decoder.decodeString(forKey: "english")
        self.chinese = decoder.decodeString(forKey: "chinese")
    }
    func encode(with coder: NSCoder) {
        coder.encode(english, forKey: "english")
        coder.encode(chinese, forKey: "chinese")
    }
}

class List: NSObject, NSCoding {
    let name: String
    fileprivate var data = Data()
    var answers: [Answer] {
        get {
            (try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)) as? [Answer] ?? []
        }
        set {
            data = (try? NSKeyedArchiver.archivedData(withRootObject: newValue, requiringSecureCoding: false)) ?? Data()
        }
    }
    init(name: String) {
        self.name = name
    }
    required init(coder decoder: NSCoder) {
        self.data = decoder.decodeData(forKey: "answersData")
        self.name = decoder.decodeString(forKey: "name")
    }
    func encode(with coder: NSCoder) {
        coder.encode(data, forKey: "answersData")
        coder.encode(name, forKey: "name")
    }
}

extension NSCoder {
    func decodeString(forKey key: String) -> String {
        return decodeObject(forKey: key) as? String ?? ""
    }
    func decodeData(forKey key: String) -> Data {
        return decodeObject(forKey: key) as? Data ?? Data()
    }
}