Как я могу сохранить объект пользовательского класса в Userdefaults в swift 5/Xcode 10.2

Я хочу сохранить массив список пациентов в UserDefaults. Пациент — это настраиваемый класс, поэтому мне нужно перенести его в объект данных, но это не работает на 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, но когда я пытаюсь это сделать, он всегда падает, и я не получаю ошибку


person fynn metzler    schedule 15.04.2019    source источник
comment
Можете ли вы также опубликовать код о Patient?   -  person denis_lor    schedule 15.04.2019
comment
Вместо этого использовать Codable? stackoverflow.com/a/48438338/2124535   -  person nathan    schedule 15.04.2019
comment
оберните его в do { try ...} catch {print(error)}, тогда вы увидите, что происходит не так   -  person Johnykutty    schedule 15.04.2019
comment
let encodeData = try! JSONEncoder().encode(patientList) если я делаю это так, я получаю эту ошибку: общий параметр «T» не может быть выведен   -  person fynn metzler    schedule 15.04.2019


Ответы (3)


Ответ 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)
    }

}

Мы расширяем наш объект пациента, чтобы он знал, что делать при сохранении.

Я использую «хранилище», чтобы его можно было поменять местами с 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) }
person Jake    schedule 15.04.2019
comment
вы должны добавить 'storage.synchronize()' в save() - person iosMentalist; 20.02.2020

Вы вообще не можете использовать 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, вы должны написать инициализатор.

person vadian    schedule 15.04.2019

person    schedule
comment
UserDefaults.standard.synchronize() устарел, и его использование не рекомендуется в течение многих лет. Также ваш ответ не добавляет ничего нового по сравнению с существующим. - person Eric Aya; 21.06.2021