置顶
菜鸟入门笔记,如有谬误之处还请大佬指出
深耕细作 笃行致远
前言
在开发IOS应用的过程中,不可避免会遇到需要存储大量复杂数据的场景,在对比如下几种方式后,我认为Core Data的支撑场景应该更为广泛,随即决定先拿下它。
- UserDefaults:一种简单键值对,值为string,类似web中的cookie
- plist:是 Property List(属性列表)的缩写,是一种用于存储和序列化数据的文件格式,它最初是由苹果公司引入的,并广泛用于 macOS 和 iOS 平台上的应用程序和配置文件中
- JSON:常见的JSON文本存储
- Core Data:Core Data是以面向对象的方式存储和管理数据的框架,其主要的底层存储方式包括 SQLite、Binary、XML、In-Memory。SQLite 作为默认的底层存储方式,无需编写SQL语句,支持事务和多种数据类型的存储
在我的理解来看,Core Data就是一种ORM框架
创建模型文件
-
新建项目时勾选Core Data,初始化模板文件中会默认创建一个模型文件和调用Core Data的示例,并完成了基本的CRUD操作
-
在现有项目中通过新建文件手动创建
填写完模型文件名后,模型文件将出现在目录树中
鉴于以学习为目的,我们采用第二种手动创建的方式
实体与属性
当模型文件创建好之后,下一步则是创建模型的实体与属性,在这里先给出几个重要的概念:
- 实体(
Entity
):对应的是数据库中的表 - 属性(
Attribute
):对应的是数据库中的字段 - 关联关系(
Relationship
):关联关系与数据库中也是一致的,通过外键来引用对方的数据,在本文中不会使用到关联关系,不做过多赘述
按照下图完成实体与属性的添加
访问模型
先不考虑封装,我们的目的是做一个最小化实现并理解Core Data
的大致调用流程
在入口文件处编写如下代码(初始化时入口文件名一般为[项目名称+App]如 BestBeforeApp.swift
,如果不知道入口文件请全局搜索 @main
):
import SwiftUI
import CoreData
@main
struct BestBeforeApp: App {
// 容器
let container: NSPersistentContainer
init(){
// 指定模型文件
container = NSPersistentContainer(name: "BestBefore")
// 加载存储类型
container.loadPersistentStores { description, error in
if let error = error {
fatalError("Unable to load persistent stores: \(error)")
}
}
}
var body: some Scene {
WindowGroup {
// 将环境变量赋给 ContentView 视图,即 ContentView.swift 文件
ContentView().environment(\.managedObjectContext, container.viewContext)
}
}
}
NSPersistentContainer
类用于管理模型、内容上下文和存储方式,贴一张官方的示意图
代码执行逻辑:
- 使用
NSPersistentContainer(name: "BestBefore")
传入模型文件名称给容器,拿到管理句柄 - 调用容器的
container.loadPersistentStores
方法去初始化存储方式,这里我们没有给出参数Core Data
会默认使用SQLite
作为底层的存储器 - 将环境变量
managedObjectContext
赋给 ContentView 视图,即 ContentView.swift 文件
如果访问失败,上述代码会抛出错误,一般情况下可能是模型文件名填写错误
新增数据
在 ContentView.swift 文件中编写代码:
import SwiftUI
struct CoreDataView: View {
// 获取 BestBeforeApp 传入的环境变量并赋值给 viewContext
@Environment(\.managedObjectContext) var viewContext
var body: some View{
Button {
addItem()
} label: {
Text("新增")
}
}
// 新增数据
func addItem(){
// 实例化一条 Item 实体的数据
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
在 let newItem = Item(context: viewContext)
中的 Item
结构体是我们在创建模型实体后,Xcode自动为我们生成的全局结构体,在项目中任何位置无需导入,可以直接使用viewContext
则是内容上下文管理器,用来管理数据的增删改查,是底层存储方式的上层封装
代码执行逻辑:
- 我们实例化了
Item
结构体,并传入viewContext
,这么做的目的是通知viewContext
将当前实例存入其缓存 - 然后通过
viewContext.save()
将缓存中的数据保存到数据库,这样的解耦意味着我们可以在save
之前做多次实体数据的操作,并一次性完成保存,而在保存之前数据都存储在缓存中,并没有真正入库
查看数据
这一步我们将刚才添加的数据展示到视图中
import SwiftUI
struct CoreDataView: View {
// 实体 Item 的数据
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
var items: FetchedResults<Item>
var body: some View{
// 循环渲染数据
ForEach(items){item in
Text(item.timestamp!,style: .date)
}
Spacer()
Button {
addItem()
} label: {
Text("新增")
}
}
...
}
代码执行逻辑:
- 我们定义了一个变量
items
来存储查询结果 - 给
items
添加了一个包装器@FetchRequest
来获取数据,并且被包装器包裹的属性会自动实现双向绑定(类似于Vue的v-model
),当数据发生变化会自动触发视图重绘,包装器的sortDescriptors
参数则允许我们按照多个属性进行升序或降序的排序,当然我猜应该也支持一些聚合函数 - 当拿到数据后在
body
中使用ForEach
遍历展示
编辑数据
编辑数据与新增数据是相同的逻辑,这里我就偷个懒,只贴一个代码片段
// 编辑数据
func editItem(item: Item){
// 更新当前数据的时间戳
item.timestamp = Date()
do {
// 注意这里同样需要保存入库
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
- 这里我们定义了一个
editItem
方法,支持传入一个Item
实例去修改其属性,当然这里我们也可以通过 @Binding 包装器将属性绑定到表单控件,这种情况下则不需要传入Item
实例,直接调用viewContext.save()
即可
删除数据
// 删除数据
func deleteItem(index: Int){
if let item = items.indices.contains(index) ? items[index] : nil{
viewContext.delete(item)
do{
try viewContext.save()
}catch{
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
代码执行逻辑:
- 我们通过给
deleteItem
传入一个items
中的索引,去找到对应数据对象(即Item的实例) - 将查找到的实例直接传入
viewContext.delete
方法中进行删除 - 删除数据库中的数据
viewContext.save()
总结
- 我们了解到Core Data是一个抽象层类ORM框架,它不直接操作底层,其底层存储方式可以有多种,默认的是SQLite
- 对
Core Data
的NSPersistentContainer
与managedObjectContext
有了初步的了解 - 通过CRUD操作,跑通了基本的数据增删改查流程,了解到 Core Data执行
save
方法前,所有的操作都是在缓存中进行,并没有真正入库
别错过精彩内容,快来关注我们的微信公众号【寻桃】!
原文链接:https://juejin.cn/post/7257441765975670841 作者:寻桃