请写一篇关于【Core Data入门:iOS数据持久化方案】的技术文章

今天也在重构
2025-12-19 00:07
阅读 294

作者:光谷码农小李,白天写Swift,晚上刷行测,目标是明年上岸湖北省考


上周五晚上10点半,我瘫在光谷软件园B2栋楼下那把被无数加班狗坐得发亮的长椅上,一边啃着热干面,一边用手机刷“湖北人事考试网”。老婆刚发来微信:“复习得怎么样了?明天模考别又睡过头。”我苦笑了一下,回了个“OK”表情包。

其实我连《申论》材料都没看完——因为白天公司新需求上线,我卡在了一个该死的本地数据存储问题上。老板说:“这个功能下周三必须上线,用户收藏的数据得持久化啊!”我心想:行吧,那就再熬个通宵搞定 Core Data 吧。

毕竟,一个想考公的程序员,不能倒在自己的技术栈上。


一、从 JavaScript 转 iOS 开发?别笑,真有人这么干

说出来你可能不信,我大学学的是前端,主攻 Vue + Node.js,工作第一份 offer 是做 Web 应用,月薪15k,房租3500(武汉关山大道合租)。但疫情那年,公司裁员,我被迫转型移动端。

起初我以为 iOS 开发就是“换个语法写 JS”,结果第一次接触 Core Data 时,直接懵了:什么 NSManagedObjectContext?NSManagedObjectModel?这命名风格比 React 的 hooks 还让人头大!

我翻遍了中文教程,很多要么照搬 Apple 官方文档(全是英文术语堆砌),要么就是“三行代码搞定存储”(实际跑起来 Crash 到怀疑人生)。最气人的是,有些所谓“实战教程”居然用 Playground 演示——可谁在真实项目里用 Playground 做持久化啊?

于是,我决定自己动手,边踩坑边总结。今天这篇,就是我熬了三个周末+两个通宵后的真实心得。不讲理论套话,只讲怎么让代码跑起来、数据存得住、App 不闪退。


二、为什么不用 SQLite 或 UserDefaults?Core Data 真香警告!

很多人一听到“数据持久化”,第一反应是:“直接用 SQLite 不就完了?”或者更懒一点:“UserDefaults 存个数组不行吗?”

我试过。真的不行。

去年十月,我负责重构公司一个新闻类 App 的“收藏夹”功能。最初用 UserDefaults 存 [String],结果用户收藏超过200条后,App 启动慢得像加载90年代网页。测试同事直接在群里@我:“小李,你这收藏功能点开要等8秒,用户早卸载了!”

后来改用 FMDB(SQLite 封装库),虽然快了,但每次增删改都要手动写 SQL,还容易出错。比如删除某条记录后忘记清理关联图片缓存,导致磁盘爆满——运维大哥差点把我从工位上拎出去。

这时候,老王(我们组 iOS 架构师,45岁,两个娃,但头发还在)拍了拍我肩膀:“试试 Core Data 吧,苹果亲儿子,自动内存管理,关系映射也方便。”

我半信半疑:“可网上都说 Core Data 复杂、坑多……”

他笑了:“复杂是因为你没用对工具。它不是数据库,是对象图管理器。你把它当 ORM 用,自然顺手。”

这句话,成了我的转折点。


##三、实战:用 Core Data 实现“收藏夹”功能(附完整流程)

下面是我上周五真正上线的代码逻辑,已脱敏,可直接复用。

步骤1:创建数据模型(.xcdatamodeld 文件)

在 Xcode 里右键 → New File → Core Data → Data Model。
新建一个 Entity 叫 Bookmark,添加属性:

  • title: String
  • url: String
  • createdAt: Date

⚠️ 注意:不要勾选 “Codegen: Class Definition”!否则你没法自定义类。选 “Manual/None”,然后手动创建 Bookmark+CoreDataClass.swiftBookmark+CoreDataProperties.swift

步骤2:初始化 Core Data Stack(关键!)

很多人在这里栽跟头。我建议把 Core Data 初始化封装成单例:

import CoreData

class CoreDataManager {
    static let shared = CoreDataManager()
    
    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "YourModelName")
        container.loadPersistentStores { _, error in
            if let error = error {
                fatalError("Core Data load failed: \(error)")
            }
        }
        return container
    }()
    
    var viewContext: NSManagedObjectContext {
        return persistentContainer.viewContext
    }
}

💡 提醒:viewContext 仅用于主线程读写!如果要做后台导入(比如从网络批量拉收藏),必须用 performBackgroundTask

步骤3:保存一条收藏

func saveBookmark(title: String, url: String) {
    let context = CoreDataManager.shared.viewContext
    let bookmark = Bookmark(context: context)
    bookmark.title = title
    bookmark.url = url
    bookmark.createdAt = Date()
    
    do {
        try context.save()
        print("✅ 收藏成功")
    } catch {
        print("❌ 保存失败: \(error)")
    }
}

步骤4:查询所有收藏(带排序)

func fetchAllBookmarks() -> [Bookmark] {
    let request: NSFetchRequest<Bookmark> = Bookmark.fetchRequest()
    request.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: false)]
    
    do {
        return try CoreDataManager.shared.viewContext.fetch(request)
    } catch {
        print("❌ 查询失败: \(error)")
        return []
    }
}

步骤5:删除(千万记得先 delete 再 save!)

func deleteBookmark(_ bookmark: Bookmark) {
    let context = CoreDataManager.shared.viewContext
    context.delete(bookmark)
    do {
        try context.save()
    } catch {
        print("❌ 删除失败: \(error)")
    }
}

四、那些让我半夜惊醒的坑(以及怎么填)

坑1:多线程访问崩溃

有次我在网络回调里直接往 viewContext 插数据,结果 App 随机闪退。日志显示:“Core Data concurrency violation”。

解决方法:所有后台操作必须用私有上下文

CoreDataManager.shared.persistentContainer.performBackgroundTask { backgroundContext in
    let bookmark = Bookmark(context: backgroundContext)
    // ...赋值
    do {
        try backgroundContext.save() // 这里 save 只影响后台上下文
        // 如果需要刷新 UI,发通知到主线程
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: .dataUpdated, object: nil)
        }
    } catch { /* handle */ }
}

坑2:模型升级(Migration)搞崩线上用户

我们有一次加了个 isRead: Bool 字段,没做轻量级迁移,结果老用户一打开 App 就白屏。

后来学会:只要改了 .xcdatamodeld,就必须创建新版本

Xcode 里选中模型文件 → Editor → Add Model Version。然后设置新版本为 “Current”。

对于简单字段变更(比如新增非空字段),勾选 “Lightweight Migration” 即可自动处理。

坑3:调试时看不到数据?

Core Data Lab(付费工具,$29)或免费的 SQLPro for SQLite 打开模拟器里的 .sqlite 文件。路径一般在:

~/Library/Developer/CoreSimulator/Devices/.../data/Containers/Data/Application/.../Documents/

看到真实数据那一刻,我感动得差点给 Apple 跪下。


五、为什么一个考公的人还在死磕技术?

可能你会问:你不是要考公吗?干嘛还花时间研究 Core Data?

说实话,我也纠结过。去年底报名省考岗位时,老婆问我:“你确定要放弃现在22k的月薪,去拿5k的公务员工资?”

我说:“不是放弃,是选择另一种稳定。但在上岸前,我得对得起这份工作,对得起自己写的每一行代码。”

技术是我的饭碗,也是我的底气。哪怕明天就去政务大厅窗口上班,今天我也要把这个收藏功能做到零 Bug。

而且你知道吗?备考和写代码其实很像

  • 行测 = 算法题,讲究速度与准确率;
  • 申论 = 代码注释,要逻辑清晰、观点鲜明;
  • 面试 = Code Review,考察你的表达与思维。

所以,我不觉得考公和写 Core Data 冲突。相反,越是焦虑的时候,越要把手头的事做到极致——因为掌控感,是唯一能对抗不确定性的武器。


六、给同样在挣扎的你:几点真心建议

  1. 别迷信“简单方案”:UserDefaults 存小配置可以,但别硬扛复杂数据。Core Data 虽重,但苹果生态里它是最稳的。
  2. 善用工具:除了上面提到的 Core Data Lab,还可以用 iOS Dev Tools 网站查 API,用 SwiftLint 规范代码。
  3. 教程要挑“带项目”的:推荐 Ray Wenderlich 的 Core Data 教程(英文但超详细),或者 Bilibili 上“即刻iOS”系列(有中文实战)。
  4. 允许自己慢一点:我花了一周才搞懂 Context 层级。没关系,程序员的成长从来不是线性的。

结语:在光谷的夜里,代码和梦想都值得被保存

昨天模考,我申论写了“数字政府建设中的数据安全与共享”,突然笑出声——这不就是我在做的事儿吗?只不过,我把“公民信息”换成了“用户收藏”,把“政务云”换成了 iCloud Sync。

也许明年此时,我已经坐在某区人社局的办公室里,敲着 Excel 而不是 Xcode。但此刻,在光谷软件园这盏凌晨一点的灯下,我依然认真地为每一个 NSManagedObjectContext 加上 do-catch,为每一次 save() 做错误兜底。

因为我知道:无论是考公还是写代码,底层逻辑都是同一件事——把重要的东西,好好存下来。

共勉。


作者备注:本文所有代码已在真实项目上线,无任何虚构。如果你也在武汉备考+打工,欢迎私信交流(但别问行测技巧,我资料分析还在错一半 😅)。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝