简体   繁体   English

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

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

I want to save the array patientList in UserDefaults.我想在 UserDefaults 中保存数组patientList。 Patient is an custom class so I need to transfer it into Data object, but this doesn't work on Swift 5 like it did before. Patient 是一个自定义类,所以我需要将它传输到 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
    }
}

This is what the function looks like.这就是函数的样子。 The problem is in the line where I initialize encodeData.问题出在我初始化 encodeData 的行中。

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

This is what Swift suggests but when I try it like this it always crashes and I don't get the error这就是 Swift 的建议,但是当我像这样尝试时,它总是崩溃并且我没有收到错误消息

You cannot use NSKeyedArchiver with structs at all. 您完全不能将NSKeyedArchiver与结构一起使用。 The objects must be subclasses of NSObject which adopt NSCoding and implement the required methods. 对象必须是采用NSCoding并实现所需方法的NSObject子类。

As suggested in the comments Codable is the better choice for example 如评论中所建议,例如, 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) }

If you want to provide default values for the Bool values you have to write an initializer. 如果要为Bool值提供默认值,则必须编写一个初始化程序。

Vadian's answer is correct, you cannot use NSKeyedArchiver with structs. Vadian的答案是正确的,您不能将NSKeyedArchiver与结构一起使用。 Having all your objects conform to Codable is the best way to reproduce the behavior you are looking for. 让您的所有对象都符合Codable是重现所寻找行为的最佳方法。 I do what Vadian does, but I you can also use protocol extensions to make this safer. 我做了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]

Introduce a protocol to manage the encoding and saving of objects. 引入协议来管理对象的编码和保存。

This does not have to inherit from Codable but it does for this example for simplicity. 这不必继承自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

}

Using protocol extensions we add an option to save an array of these objects. 使用协议扩展,我们添加了一个选项来保存这些对象的数组。

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

}

We extend our patient object so it can know what to do when saving. 我们扩展了耐心的对象,以便它可以知道保存时该怎么做。

I use "storage" so that this could be swapped with NSKeychain. 我使用“存储”,以便可以与NSKeychain交换。 If you are saving sensitive data (like patient information) you should be using the keychain instead of UserDefaults. 如果要保存敏感数据(例如患者信息),则应使用钥匙串而不是UserDefaults。 Also, make sure you comply with security and privacy best practices for health data in whatever market you're offering your app. 此外,请确保您在提供应用程序的任何市场中都遵守健康数据的安全性和隐私最佳做法。 Laws can be a very different experience between countries. 法律在国家之间可能是截然不同的经验。 UserDefaults might not be safe enough storage. UserDefaults可能不够安全。

There are lots of great keychain wrappers to make things easier. 有很多很棒的钥匙串包装器可以简化事情。 UserDefaults simply sets data using a key. UserDefaults只需使用键即可设置数据。 The Keychain does the same. 钥匙串的作用相同。 A wrapper like https://github.com/evgenyneu/keychain-swift will behave similar to how I use UserDefaults below. https://github.com/evgenyneu/keychain-swift这样的包装器的行为类似于我在下面使用UserDefaults的行为。 I have commented out what the equivalent use would look like for completeness. 我已注释掉等效用法的完整性。

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

Saving is simplified, check out the 2 examples below! 简化了保存过程,请查看以下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")
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM