本篇目标
综合前面几种持久化方案,掌握本地数据设计的思路,能根据数据量、读写频率、安全要求选择合适的存储方式,理解缓存、离线优先、数据同步与迁移的基本策略。
一、四种持久化方案回顾
方案 | 适用数据 | 特点 |
|---|---|---|
UserDefaults | 少量键值、偏好设置 | 简单快速,不适合大量数据 |
文件读写 | 文本、JSON、二进制 | 灵活,适合结构化数据 |
Core Data | 对象图、关系数据 | 面向对象,与 SwiftUI 集成好 |
SQLite / GRDB | 复杂查询、大量数据 | 性能高,SQL 控制强 |
选择原则:
数据量小、结构简单 → UserDefaults
数据量中等、需要序列化 → 文件读写
对象关系复杂、iOS 原生 → Core Data
查询复杂、性能要求高 → SQLite / GRDB
二、数据分类与存储选型

按数据特征分类:
数据类型 | 示例 | 推荐存储 |
|---|---|---|
用户偏好 | 主题、字号、语言 | UserDefaults / @AppStorage |
登录凭证 | Token、密码 | Keychain |
业务数据 | 笔记、订单、列表 | Core Data / SQLite |
缓存数据 | 图片、接口结果 | 文件 / Caches 目录 |
配置数据 | 静态配置、模板 | 文件 / Bundle |
日志数据 | 运行日志、错误记录 | 文件 / 按日期分文件 |
判断标准:
数据量有多大?
读写频率多高?
是否需要查询和排序?
是否需要关系?
是否敏感?
是否允许丢失?
三、数据结构设计
设计步骤:
列出所有需要存储的数据。
按功能模块分组。
定义每个数据的字段与类型。
确定数据之间的关系。
确定读写场景与频率。
选择合适的存储方案。
示例:笔记应用
Note(笔记)
├── id: String
├── title: String
├── content: String
├── createdAt: Date
├── updatedAt: Date
└── categoryId: String?
Category(分类)
├── id: String
├── name: String
└── createdAt: Date
Setting(设置)
├── theme: String
├── fontSize: Int
└── sortBy: String存储分配:
Note、Category → Core Data / SQLite
Setting → UserDefaults / @AppStorage
用户登录凭证 → Keychain
四、数据模型设计原则
单一职责:一个模型只描述一类数据。
字段命名规范:
用英文小写驼峰。
语义明确,不要缩写。
时间字段统一用
createdAt、updatedAt。
字段类型选择:
字段 | 推荐类型 |
|---|---|
唯一标识 | UUID / String |
标题、名称 | String |
内容 | String |
数量 | Int |
金额 | Decimal |
时间 | Date |
状态 | Enum(用原始值存储) |
可选性设计:
必要字段设为非空。
可能为空的字段设为可选。
提供默认值,避免空值导致崩溃。
五、离线优先设计
离线优先(Offline First):先读写本地,再同步网络。
流程:
用户操作 → 写入本地 → 界面立即刷新 → 后台同步服务器优势:
离线可用。
响应速度快。
弱网体验好。
关键设计:
本地数据是主数据源。
网络同步是后台任务。
同步失败可重试。
冲突有解决策略。
同步状态标记:
enum SyncStatus: String, Codable {
case pending // 待同步
case synced // 已同步
case failed // 同步失败
}注意:
不是所有应用都需要离线优先。
简单应用可以先在线后本地。
复杂同步逻辑需谨慎设计。
📌 进阶内容:缓存策略已放到第 8 章《缓存策略》;数据迁移与备份已放到第 6 章《数据迁移与备份》。基础篇先掌握"存哪儿、怎么选"即可。
六、数据安全
敏感数据分级:
数据 | 处理方式 |
|---|---|
普通业务数据 | 本地存储 |
登录 Token | Keychain |
密码 | Keychain + 加密 |
支付信息 | 不本地存储 |
个人隐私 | 加密 + 最小化存储 |
Keychain 简介:
import Security
func saveToKeychain(key: String, value: String) {
let data = value.data(using: .utf8)!
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data
]
SecItemDelete(query as CFDictionary)
SecItemAdd(query as CFDictionary, nil)
}文件加密:
使用 CryptoKit 加密敏感文件。
使用文件保护等级。
注意:
不要明文存储密码。
不要将敏感信息写入日志。
不要将敏感数据上传到第三方。
七、数据量增长应对
问题:
数据量增长导致查询变慢。
存储空间占用增加。
启动加载时间变长。
应对策略:
策略 | 说明 |
|---|---|
分页查询 | 每次只加载一部分 |
索引优化 | 为常用查询字段加索引 |
数据归档 | 旧数据归档到文件 |
定期清理 | 删除过期数据 |
懒加载 | 需要时才加载 |
分页示例:
request.fetchOffset = page * pageSize
request.fetchLimit = pageSize归档示例:
func archiveOldNotes(before date: Date) {
let request: NSFetchRequest<Note> = Note.fetchRequest()
request.predicate = NSPredicate(format: "createdAt < %@", date as NSDate)
let oldNotes = try? context.fetch(request)
// 导出到归档文件,然后删除
}八、综合设计示例
需求:一个笔记应用,支持分类、搜索、离线使用。
存储设计:
数据 | 存储方案 | 说明 |
|---|---|---|
笔记 | Core Data | 支持查询、排序、关系 |
分类 | Core Data | 与笔记一对多 |
用户设置 | UserDefaults | 主题、字号、排序 |
登录凭证 | Keychain | Token |
图片附件 | 文件系统 | Documents 目录 |
网络缓存 | Caches | 图片、接口结果 |
目录结构:
Documents/
├── Attachments/ // 图片附件
└── Backup/ // 备份文件
Library/
├── Caches/ // 缓存
└── Preferences/ // UserDefaults模块划分:
Data Layer/
├── NoteRepository // 笔记数据操作
├── CategoryRepository // 分类数据操作
├── SettingsStore // 设置存储
├── KeychainStore // 凭证存储
└── FileStorage // 文件存储九、避坑指南
不要所有数据都放 UserDefaults:只适合少量键值。
不要忽略数据迁移:版本升级必须考虑。
不要明文存敏感数据:用 Keychain 或加密。
缓存要有失效策略:否则会占满磁盘。
不要在错误的线程操作数据库:注意线程安全。
不要忽略分页:大数据量必须分页。
不要忘记备份:用户数据丢失是严重问题。
不要过度设计:简单场景用简单方案。
十、检查清单
[ ] 能根据数据特征选择存储方案
[ ] 会设计数据结构与字段
[ ] 理解离线优先的设计思路
[ ] 掌握缓存策略与失效机制
[ ] 了解 Keychain 与数据加密
[ ] 会做数据迁移
[ ] 能实现数据备份与恢复
[ ] 能应对数据量增长
[ ] 能设计完整的数据存储方案
📦 三种存储方案的实现在 HelloWorld 的 Data/ 目录下,共用同一个 NoteStore 协议,可以在「设置」页切换对比。