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

Я хочу сохранить массив PatientList в UserDefaults.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
    }
}

Так выглядит функция.Проблема в строке, где я инициализирую encodeData.

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

Это то, что предлагает Swift, но когда я пытаюсь сделать это таким образом, он всегда вылетает, и я не получаю ошибку

Ответы [ 2 ]

1 голос
/ 15 апреля 2019

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

0 голосов
/ 15 апреля 2019

Vadian ответ правильный, вы не можете использовать NSKeyedArchiver со структурами. Приведение всех ваших объектов в соответствие с Codable - лучший способ воспроизвести искомое поведение. Я делаю то же, что и Вадиан, но вы также можете использовать расширения протокола, чтобы сделать это безопаснее.

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

Экономия упрощена, ознакомьтесь с двумя примерами ниже!

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