漫谈初学者处理 CoreData 数据之启示录

概览

小伙伴们都知道要想在 App 中持久保持数据,使用 CoreData 是一个非常明智的选择。它简洁、安全并且富有表现力。更重要的是它是 Apple 的"亲生儿子",所以兼容性更是没的说。

头发茂盛的初学者们在使用 CoreData 时总会遇到各种"千奇百怪"的问题,在这里我们就从中抽选出几个比较有启发的小场景和大家一道闲聊一番吧。

在本篇博文中,您将学到如下内容:

  1. 如何写一个通用删除所有托管对象的方法?
  2. 如何查询 UUID 类型的字段?
  3. 2025.03.11 更新
  4. 一个常见的查询错误

相信学完本课后,小伙伴们又会对 CoreData 的使用更加胸有成竹、胜券在握了。

那还等什么呢?Let's go!:)


1. 如何写一个通用删除所有托管对象的方法?

在很多情况下,我们会在 CoreData 中创建各种托管类。我们会有一些非常通用的需求,比如删除这些托管类对应的所有托管对象以便重置数据库。

下面是一个 People 托管类删除其所有托管对象的代码示例:

swift 复制代码
extension People {
    static func getAll(_ moc: NSManagedObjectContext) throws -> [People] {
        let req: NSFetchRequest<People> = fetchRequest()
        
        return try moc.fetch(req)
    }
    
    static func deleteAll(_ moc: NSManagedObjectContext) throws {
        let all = try getAll(moc)
        
        for obj in all {
            moc.delete(obj)
        }
        
        try moc.save()
    }
}

这样做乍一看并没有什么问题,只不过如果你有几十个类似 People 的托管类都要实现类似的删除功能,你是否愿意在每个类里面都来上这么一段呢?

当然不!它显然严重违反了 DRY 和 KISS 原则!

由于我们定义的所有托管类实际上都派生自 NSManagedObject 类,所以我们只需在 NSManagedObject 里开动脑筋"摧城拔寨"即可:

swift 复制代码
extension NSManagedObject {
    static func deleteAll(_ moc: NSManagedObjectContext) throws {
        let req = NSFetchRequest<Self>(entityName: "\(Self.self)")
        
        let results = try moc.fetch(req)
        for obj in results {
            moc.delete(obj)
        }

        try moc.save()
    }
}

如上所示,我们在 NSManagedObject 里直接实现了通用的 deleteAll 方法,现在所有托管类都可以"草船借箭"啦:

swift 复制代码
People.deleteAll()

Hero.deleteAll()

Panda.deleteAll()

2. 如何查询 UUID 类型的字段?

我们在创建托管类时可以为其选择各种字段类型,其中一个颇为常用的是 UUID 类型:

那么问题来了:我们怎么以此(UUID)类型来查询对应的托管对象呢?

有的小伙伴可能会直接这样写:

swift 复制代码
let req: NSFetchRequest<Self> = NSFetchRequest(entityName: "\(Self.self)")
        
let id = UUID()
req.predicate = NSPredicate(format: "cid = %@", id)

不幸的是,这样会立即引起编译器的"大声疾呼":

别急,我们有 3 种方法可以解决此问题,第一种是将 UUID 转换为 NSUUID 类型:

swift 复制代码
req.predicate = NSPredicate(format: "cid = %@", id as NSUUID)

第二种方法是将 id 包裹到实参数组(argumentArray)中去:

swift 复制代码
req.predicate = NSPredicate(format: "cid = %@", argumentArray: [id])

最后一种方法更直截了当,其实 UUID 和其对应的底层表示对于 CoreData(Sqlite)来说基本可以认为是同一种东西:


2025.03.11 更新

所以,我们可以在查询的 NSPredicate 里直接使用 UUID 对应的字符串:

swift 复制代码
// req.predicate = NSPredicate(format: "cid = %@", id.uuidString)

现在我们又可以与 UUID 字段查询愉快的玩耍啦,很赞哦!

根据在 iOS 18 中测试的最新结果,上面用 UUID#uuidString 这种方式查询已不能得到正确的结果。

我不确定是之前的疏忽还是新系统改变了 UUID 类型底层的存储方式,如果哪位小伙伴有相关答案欢迎告诉我。👌


3. 一个常见的查询错误

在 CoreData 数据的查询中,我们往往需要按照托管类指定的属性(或字段)来进行特定的查询:

swift 复制代码
extension NSManagedObject {
    // 查找 idKey 字段为 id 值的托管对象
    static func uniqueWithID(_ id: UUID, idKey: String, moc: NSManagedObjectContext) throws -> Self? {
        let req: NSFetchRequest<Self> = NSFetchRequest(entityName: "\(Self.self)")
        req.predicate = NSPredicate(format: "%@ = %@", idKey, id.uuidString)
        
        let results = try moc.fetch(req)
        if results.count > 1 {
            throw AppError.extraSingletonManagedObject
        }
        return results.first
    }
}

如果用上面的 uniqueWithID() 方法进行托管对象的查询,你将啥都找不到!聪明的小伙伴们可以看出问题出在哪吗?

元凶就是 NSPredicate 的格式

我们先来看看上面查询格式实际产生了什么 SQL 语句:

了然了吗?此时 CoreData 把字段名当成了一个字符串(gid),我们必须用 %K 指示器来绑定查询中字段的参数才能产生正确的 SQL 语句:

swift 复制代码
req.predicate = NSPredicate(format: "%K = %@", idKey, id.uuidString)

以上就是献给 CoreData 初学者的三则小启发,相信大家可以有所收益,给自己点一个大大的赞吧!💯

总结

在本篇博文中,我们介绍了初学者在处理 CoreData 各种数据时一些有用的小启示,希望大家能够喜欢。

感谢观赏,再会!8-)

相关推荐
程序猿ZhangSir8 分钟前
Redis 缓存进阶篇,缓存真实数据和缓存文件指针最佳实现?如何选择?
数据库·redis·缓存
yjb.gz10 分钟前
Oracle函数JSON_TABLE使用
数据库·oracle·json
大熊猫侯佩23 分钟前
Swift 数学计算:用 Accelerate 框架让性能“加速吃鸡”
算法·swift
大熊猫侯佩26 分钟前
Swift 6.2 并发江湖:两大神功破局旧制,代码运行经脉革新(下)
swiftui·swift·wwdc
大熊猫侯佩28 分钟前
Swift 6.2 并发江湖:两大神功破局旧制,代码运行经脉革新(上)
swiftui·swift·wwdc
大熊猫侯佩37 分钟前
SwiftUI 7 江湖新风:WWDC25 揭晓神秘武林志
swiftui·swift·wwdc
大熊猫侯佩42 分钟前
SwiftUI 7(iOS 26 / iPadOS 26)中玻璃化标签页的全新玩法
swiftui·swift·apple
Dubhehug44 分钟前
4.B树和B+树的区别?为什么MySQL选择B+树作为索引?
数据库·b树·mysql·面试·b+树
Daniel_Coder1 小时前
iOS Widget 开发-1:什么是 iOS Widget?开发前的基本认知
ios·swiftui·swift·widget
linux修理工1 小时前
n1 armbian 安装桌面环境并启用xrdp远程登录
linux·服务器·数据库