本文主要介绍的是,代码中存在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/