本篇目标

综合前面几种持久化方案,掌握本地数据设计的思路,能根据数据量、读写频率、安全要求选择合适的存储方式,理解缓存、离线优先、数据同步与迁移的基本策略。

一、四种持久化方案回顾

方案

适用数据

特点

UserDefaults

少量键值、偏好设置

简单快速,不适合大量数据

文件读写

文本、JSON、二进制

灵活,适合结构化数据

Core Data

对象图、关系数据

面向对象,与 SwiftUI 集成好

SQLite / GRDB

复杂查询、大量数据

性能高,SQL 控制强

选择原则:

  • 数据量小、结构简单 → UserDefaults

  • 数据量中等、需要序列化 → 文件读写

  • 对象关系复杂、iOS 原生 → Core Data

  • 查询复杂、性能要求高 → SQLite / GRDB

二、数据分类与存储选型

四种本地存储方案选型对照

按数据特征分类:

数据类型

示例

推荐存储

用户偏好

主题、字号、语言

UserDefaults / @AppStorage

登录凭证

Token、密码

Keychain

业务数据

笔记、订单、列表

Core Data / SQLite

缓存数据

图片、接口结果

文件 / Caches 目录

配置数据

静态配置、模板

文件 / Bundle

日志数据

运行日志、错误记录

文件 / 按日期分文件

判断标准:

  1. 数据量有多大?

  2. 读写频率多高?

  3. 是否需要查询和排序?

  4. 是否需要关系?

  5. 是否敏感?

  6. 是否允许丢失?

三、数据结构设计

设计步骤:

  1. 列出所有需要存储的数据。

  2. 按功能模块分组。

  3. 定义每个数据的字段与类型。

  4. 确定数据之间的关系。

  5. 确定读写场景与频率。

  6. 选择合适的存储方案。

示例:笔记应用

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 协议,可以在「设置」页切换对比。