漫谈初学者处理 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-)

相关推荐
文牧之3 分钟前
PostgreSQL的扩展lo
运维·数据库·postgresql
我科绝伦(Huanhuan Zhou)7 分钟前
MOP数据库备份脚本生成工具
前端·css·数据库
步行cgn9 分钟前
MySQL 中 DISTINCT 去重的核心注意事项详解
数据库·mysql
南棱笑笑生21 分钟前
20250617在ubuntu20.04.6下编译飞凌OK3576-C_Linux6.1.75_用户资料_R1(更新日期_20241014)
数据库
花开月满西楼1 小时前
Android实例项目【智能家居系统】实现数据库登录注册+动画效果+网页跳转+短信发送!!!
android·数据库·智能家居
洗发水很好用2 小时前
制造部门的转型目标与场景痛点
大数据·数据库·制造
芳菲菲其弥章2 小时前
【数据分析三:Data Storage】数据存储
数据库·数据挖掘·数据分析
web守墓人2 小时前
【go】(仅思路)使用go实现一款简单的关系型数据库gosql
开发语言·数据库·golang
码码不爱我4 小时前
报错:Maven无法解析插件 org.apache.maven.plugins:maven-surefire-plugin:3.0.0
java·数据库·maven
荔枝吻4 小时前
【沉浸式解决问题】优化MySQL中多表union速度慢的问题
数据库·mysql·union