更改结构时防止 AppStorage 上的数据丢失

Prevent lost of data on AppStorage when changing a struct

我有一个数据模型来处理应用程序使用的结构和数据。我正在使用 AppStorage.

保存该数据

我最近需要向该结构添加一个额外的值,当我这样做时,所有保存的数据都消失了。

有什么办法可以避免这种情况吗?我在 Apple 的文档或其他 Swift 或 SwiftUI 网站上找不到任何关于此的内容。

这是我的数据结构以及我如何保存它。

let dateFormatter = DateFormatter()

struct NoteItem: Codable, Hashable, Identifiable {
    let id: UUID
    var text: String
    var date = Date()
    var dateText: String {
        dateFormatter.dateFormat = "EEEE, MMM d yyyy, h:mm a"
        return dateFormatter.string(from: date)
    }
    var tags: [String] = []
    //var starred: Int = 0 // if I add this, it wipes all the data the app has saved
}

final class DataModel: ObservableObject {
    @AppStorage("myappdata") public var notes: [NoteItem] = []
    
    init() {
        self.notes = self.notes.sorted(by: {
            [=10=].date.compare(.date) == .orderedDescending
        })
    }
    
    func sortList() {
        self.notes = self.notes.sorted(by: {
            [=10=].date.compare(.date) == .orderedDescending
        })
    }
}

extension Array: RawRepresentable where Element: Codable {
    public init?(rawValue: String) {
        guard let data = rawValue.data(using: .utf8),
              let result = try? JSONDecoder().decode([Element].self, from: data)
        else {
            return nil
        }
        self = result
    }
    
    public var rawValue: String {
        guard let data = try? JSONEncoder().encode(self),
              let result = String(data: data, encoding: .utf8)
        else {
            return "[]"
        }
        return result
    }
}

我当然同意 UserDefaults (AppStorage) 不是最佳选择,但无论您选择哪种存储解决方案,您都需要一个迁移策略。因此,您可以采用以下两条路线来迁移已更改的 json 结构。

第一个是向您的结构添加自定义 init(from:) 并单独处理新的 属性

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    id = try container.decode(UUID.self, forKey: .id)
    text = try container.decode(String.self, forKey: .text)
    date = try container.decode(Date.self, forKey: .date)
    tags = try container.decode([String].self, forKey: .tags)
    if let value = try? container.decode(Int.self, forKey: .starred) { 
        starred = value
    } else {
        starred = 0
    }
}

另一种选择是用另一个名字保留旧版本的结构,如果普通结构解码失败则使用它,然后将结果转换为新结构

extension NoteItem {
    static func decode(string: String) -> [NoteItem]? {
        guard let data = string.data(using: .utf8) else { return nil }

        if let result = try? JSONDecoder().decode([NoteItem].self, from: data) {
            return result
        } else if let result = try? JSONDecoder().decode([NoteItemOld].self, from: data) {
            return result.map { NoteItem(id: [=11=].id, text: [=11=].text, date: [=11=].date, tags: [=11=].tags, starred: 0)}
        }
        return nil
    }
}