如何在 swift 5/ Xcode 10.2 的 Userdefaults 中保存自定义 class 的对象

How can I save an object of a custom class in Userdefaults in swift 5/ Xcode 10.2

我想将数组 patientList 保存在 UserDefaults 中。 Patient 是自定义的 class,因此我需要将其传输到 Data 对象中,但这在 Swift 5 上并不像以前那样起作用。

func addFirstPatient(){
    let newPatient = Patient(name: nameField.text!, number: numberField.text!, resultArray: resultArray, diagnoseArray: diagnoseArray)
    let patientList: [Patient] = [newPatient]
    let encodeData: Data = NSKeyedArchiver.archivedData(withRootObject: patientList)
    UserDefaults.standard.set(encodeData, forKey: "patientList")
    UserDefaults.standard.synchronize()
}
struct Patient {
    var diagnoseArray: [Diagnose]
    var resultArray: [Diagnose]
    var name: String
    var number: String
    init(name: String, number: String, resultArray: [Diagnose], diagnoseArray: [Diagnose]) {
        self.diagnoseArray = diagnoseArray
        self.name = name
        self.number = number
        self.resultArray = resultArray
    }
}
struct Diagnose{
    var name: String
    var treatments: [Treatment]
    var isPositiv = false
    var isExtended = false
    init(name: String, treatments: [Treatment]) {
        self.name = name
        self.treatments = treatments
    }
}
struct Treatment {
    var name: String
    var wasMade = false
    init(name: String) {
        self.name = name
    }
}

这就是函数的样子。 问题出在我初始化 encodeData 的那一行。

let encodeData: Data = try! NSKeyedArchiver.archivedData(withRootObject: patientList, requiringSecureCoding: false)

这就是 Swift 的建议,但是当我这样尝试时,它总是崩溃并且我没有收到错误

您根本不能将 NSKeyedArchiver 与结构一起使用。这些对象必须是 NSObject 的子类,它们采用 NSCoding 并实现所需的方法。

如评论中所建议,例如 Codable 是更好的选择

struct Patient : Codable {
    var name: String
    var number: String
    var resultArray: [Diagnose]
    var diagnoseArray: [Diagnose]
}

struct Diagnose : Codable {
    var name: String
    var treatments: [Treatment]
    var isPositiv : Bool
    var isExtended : Bool
}

struct Treatment  : Codable {
    var name: String
    var wasMade : Bool
}

let newPatient = Patient(name: "John Doe",
                         number: "123",
                         resultArray: [Diagnose(name: "Result", treatments: [Treatment(name: "Treat1", wasMade: false)], isPositiv: false, isExtended: false)],
                         diagnoseArray: [Diagnose(name: "Diagnose", treatments: [Treatment(name: "Treat2", wasMade: false)], isPositiv: false, isExtended: false)])
let patientList: [Patient] = [newPatient]
do {
    let encodeData = try JSONEncoder().encode(patientList)
    UserDefaults.standard.set(encodeData, forKey: "patientList")
    // synchronize is not needed
} catch { print(error) }

如果您想为 Bool 值提供默认值,您必须编写初始化程序。

Vadian 的回答是正确的,您不能将 NSKeyedArchiver 与结构一起使用。让所有对象都符合 Codable 是重现您正在寻找的行为的最佳方式。我做 Vadian 做的事,但我也可以使用协议扩展来使它更安全。

import UIKit

struct Patient: Codable {
    var name: String
    var number: String
    var resultArray: [Diagnose]
    var diagnoseArray: [Diagnose]
}

struct Diagnose: Codable {
    var name: String
    var treatments: [Treatment]
    var isPositiv : Bool
    var isExtended : Bool
}

struct Treatment: Codable {
    var name: String
    var wasMade : Bool
}

let newPatient = Patient(name: "John Doe",
                         number: "123",
                         resultArray: [Diagnose(name: "Result", treatments: [Treatment(name: "Treat1", wasMade: false)], isPositiv: false, isExtended: false)],
                         diagnoseArray: [Diagnose(name: "Diagnose", treatments: [Treatment(name: "Treat2", wasMade: false)], isPositiv: false, isExtended: false)])
let patientList: [Patient] = [newPatient]

引入一个协议来管理对象的编码和保存。

这不一定要继承自 Codable,但为了简单起见,它确实适用于此示例。

/// Objects conforming to `CanSaveToDisk` have a save method and provide keys for saving individual objects or a list of objects.
protocol CanSaveToDisk: Codable {

    /// Provide default logic for encoding this value.
    static var defaultEncoder: JSONEncoder { get }

    /// This key is used to save the individual object to disk. This works best by using a unique identifier.
    var storageKeyForObject: String { get }

    /// This key is used to save a list of these objects to disk. Any array of items conforming to `CanSaveToDisk` has the option to save as well.
    static var storageKeyForListofObjects: String { get }

    /// Persists the object to disk.
    ///
    /// - Throws: useful to throw an error from an encoder or a custom error if you use stage different from user defaults like the keychain
    func save() throws

}

我们使用协议扩展添加了一个选项来保存这些对象的数组。

extension Array where Element: CanSaveToDisk {

    func dataValue() throws -> Data {
        return try Element.defaultEncoder.encode(self)
    }

    func save() throws {
        let storage = UserDefaults.standard
        storage.set(try dataValue(), forKey: Element.storageKeyForListofObjects)
    }

}

我们扩展了我们的患者对象,这样它就可以知道在保存时要做什么。

我使用 "storage" 以便它可以与 NSKeychain 交换。如果您正在保存敏感数据(如患者信息),您应该使用钥匙串而不是 UserDefaults。此外,请确保您在提供应用程序的任何市场中都遵守有关健康数据的安全和隐私最佳实践。不同国家的法律可能是截然不同的体验。 UserDefaults 可能不够安全。

有很多很棒的钥匙扣包装器可以让事情变得更简单。 UserDefaults 只是使用键设置数据。钥匙串也是如此。像 https://github.com/evgenyneu/keychain-swift 这样的包装器的行为类似于我在下面使用 UserDefaults 的方式。为了完整起见,我已经注释掉了等效用法。

extension Patient: CanSaveToDisk {

    static var defaultEncoder: JSONEncoder {
        let encoder = JSONEncoder()
        // add additional customization here
        // like dates or data handling
        return encoder
    }

    var storageKeyForObject: String {
        // "com.myapp.patient.123"
        return "com.myapp.patient.\(number)"
    }

    static var storageKeyForListofObjects: String {
        return "com.myapp.patientList"
    }

    func save() throws {

        // you could also save to the keychain easily
        //let keychain = KeychainSwift()
        //keychain.set(dataObject, forKey: storageKeyForObject)

        let data = try Patient.defaultEncoder.encode(self)
        let storage = UserDefaults.standard
        storage.setValue(data, forKey: storageKeyForObject)
    }
}

保存很简单,请查看下面的 2 个示例!

do {

    // saving just one patient record
    // this saves this patient to the storageKeyForObject
    try patientList.first?.save()

    // saving the entire list
    try patientList.save()


} catch { print(error) }
struct Employee: Codable{
  var name: String
}

var emp1 = Employee(name: "John")
 let encoder = JSONEncoder()
    do {
        let data = try encoder.encode(emp1)
        UserDefaults.standard.set(data, forKey: "employee")
        UserDefaults.standard.synchronize()
    } catch {
        print("error")
    }