Swift JSON数据解码保存到SwiftData
Swift JSON数据解码保存到SwiftData

Swift JSON数据解码保存到SwiftData

本文主要介绍的是,代码中存在JSON数据解码并存储的变量,当设置SwiftDatas2,需要将JSON数据解码保存到SwiftData中。后续从SwiftData中获取并修改数据。

未使用SwiftData

在原来的ContentView视图当中,resorts变量是从Bundle中查找并解码的数据:

struct ContentView: View {
    @State private var favorites = Favorites()
    @State private var searchText = ""
    let resorts: [Resort] = Bundle.main.decode("resorts.json")
    ...
}

这里的resorts变量会将resorts.json文件解码并存储起来,decode是Bundle的扩展:

extension Bundle {
    func decode<T: Decodable>(_ file: String) -> T {
        guard let url = self.url(forResource: file, withExtension: nil) else {
            fatalError("Failed to locate \(file) in bundle.")
        }

        guard let data = try? Data(contentsOf: url) else {
            fatalError("Failed to load \(file) from bundle.")
        }

        let decoder = JSONDecoder()

        do {
            return try decoder.decode(T.self, from: data)
        } catch DecodingError.keyNotFound(let key, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' – \(context.debugDescription)")
        } catch DecodingError.typeMismatch(_, let context) {
            fatalError("Failed to decode \(file) from bundle due to type mismatch – \(context.debugDescription)")
        } catch DecodingError.valueNotFound(let type, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing \(type) value – \(context.debugDescription)")
        } catch DecodingError.dataCorrupted(_) {
            fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON.")
        } catch {
            fatalError("Failed to decode \(file) from bundle: \(error.localizedDescription)")
        }
    }
}

当上面的代码需要转换为SwiftData时,就无法从Bundle中解码赋值,而应该从SwiftData中查找数据。

使用SwiftData

struct hackingwithswiftApp: App {
    
    @State private var modelContainer: ModelContainer = {
        do {
            // 创建 SwiftData 的 ModelContainer
            return try ModelContainer(for: Resort.self)
        } catch {
            fatalError("Failed to create ModelContainer: \(error)")
        }
    }()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .modelContainer(modelContainer)
                .onAppear {
                    loadInitialDataIfNeeded()
                }
        }
    }
    
    @MainActor 
    private func loadInitialDataIfNeeded() {
        let context = modelContainer.mainContext
        
        // 检查数据库中是否已有数据
        let fetchRequest = FetchDescriptor<Resort>()
        if (try? context.fetch(fetchRequest))?.isEmpty == true {
            // 数据库为空时加载 JSON 数据
            if let resorts: [Resort] = Bundle.main.decode("resorts.json") {
                for resort in resorts {
                    context.insert(resort) // 将每个 Resort 插入到 SwiftData 数据库
                }
                try? context.save() // 保存到数据库
            }
        }
    }
}

在应用入口文件,创建一个初始数据的方法,因为需要获取ModelContainer的mainContext,因此需要使用@MainActor进行标注。

代码解析

1、定义ModelContainer
@State private var modelContainer: ModelContainer = {
    do {
        return try ModelContainer(for: Resort.self)
    } catch {
        fatalError("Failed to create ModelContainer: \(error)")
    }
}()

ModelContainer 是 SwiftData 提供的核心数据管理容器。

它管理与 Resort 模型(代表的数据实体)的所有交互,例如数据的保存、加载等。

作用

初始化 ModelContainer,绑定模型 Resort。

如果初始化失败,会抛出错误并终止程序运行(通过 fatalError 处理)。

@State:由于 ModelContainer 是一个状态对象,它需要被 SwiftUI 管理。

2、SwiftUI的场景和视图
var body: some Scene {
    WindowGroup {
        ContentView()
            .modelContainer(modelContainer)
            .onAppear {
                loadInitialDataIfNeeded()
            }
    }
}

.modelContainer(modelContainer):将 ModelContainer 传递到视图中,使其可以访问和使用 SwiftData 的上下文。

.onAppear:当视图加载时调用 loadInitialDataIfNeeded 方法,检查并加载初始数据。

3、加载初始数据
@MainActor
private func loadInitialDataIfNeeded() {
    let context = modelContainer.mainContext
    
    // 检查数据库中是否已有数据
    let fetchRequest = FetchDescriptor<Resort>()
    if (try? context.fetch(fetchRequest))?.isEmpty == true {
        // 数据库为空时加载 JSON 数据
        if let resorts: [Resort] = Bundle.main.decode("resorts.json") {
            for resort in resorts {
                context.insert(resort) // 将每个 Resort 插入到 SwiftData 数据库
            }
            try? context.save() // 保存到数据库
        }
    }
}

目的:确保数据库中有数据。如果数据库为空,从 JSON 文件中加载数据并插入到数据库。

步骤

1、获取 ModelContainer 的 mainContext(主上下文)。

2、执行查询,检查是否有 Resort 数据。

3、如果没有数据:

从应用的 Bundle 中加载 resorts.json 文件。

将 JSON 数据解码为 Resort 数组。

将每个 Resort 实体插入到 SwiftData 数据库。

调用 context.save() 将更改保存到数据库。

4、@MainActor 标记

保证 loadInitialDataIfNeeded 在主线程执行,因为:

数据库上下文 mainContext 的操作必须在主线程上完成。

确保和用户界面更新的线程一致,避免多线程问题。

总结

上述代码定义了一个应用程序,使用 SwiftData 来管理 Resort 模型的数据。

它会在首次启动时检查数据库中是否有初始数据,如果没有就从 JSON 文件中加载数据。

它充分利用了 SwiftUI 和 SwiftData 提供的现代数据管理功能,使得数据加载和 UI 构建简单直观。

相关文章

1、Swift主线程标记@MainActor:https://fangjunyu.com/2024/12/25/swift%e4%b8%bb%e7%ba%bf%e7%a8%8b%e6%a0%87%e8%ae%b0mainactor/

2、SwiftData框架属性包装器@ModelContext: https://fangjunyu.com/2024/11/05/swiftdata%e6%a1%86%e6%9e%b6%e5%b1%9e%e6%80%a7%e5%8c%85%e8%a3%85%e5%99%a8modelcontext/

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注