Tech note
全局搜索技术路线
这篇记录 3x3 iOS 全局搜索的实现路线:让输入跟手、把纯空白搜索当作大结果浏览,并减少快速输入时 SwiftUI 列表闪烁。
目标
- 输入框必须跟手,不被 Core Data 查询、结果分类或列表刷新卡住。
- 普通关键词支持跨 Schedule、Task、Tag、Note 搜索,并能按命中来源筛选。
- 纯空白搜索要可用,但它本质是大结果浏览,不能按普通关键词路径处理。
- 快速修改搜索词时界面要稳定,避免列表先清空、再 loading、再重建。
输入分流
| 输入 | 含义 | UI 行为 | 查询策略 |
|---|---|---|---|
"" |
空搜索 | 显示快捷入口 | 清空结果,不查库 |
" " / 多个空格 |
大结果浏览 | 保留旧结果 + spinner,先显示首批结果 | 纯空白 fast path |
| 普通文字 | 精确搜索 | 保留旧结果 + spinner,结果回来后替换 | 普通关键词路径 |
纯空白不要等同于普通关键词。普通 CONTAINS " " 会命中极大数据集,还会触发多层关联谓词,体验最差。
普通关键词路径
普通关键词覆盖标题、备注、标签名和关联待办名。后台 fetch 只返回 NSManagedObjectID,回主 context 后再解析对象,避免输入路径被同步 fetch 拖住。
@Task Name 只表示待办名字,不包含关联待办备注。否则会出现胶囊显示命中,但列表可见待办名里看不到关键词。
纯空白 fast path
- Schedule 只保留合法时间线基础条件,按
create_date倒序取。 - Task、Tag、Note 不做
CONTAINS " ",直接按各自排序取。 - 首批使用
fetchLimit,当前首屏预览数量为90。 - 首批结果回来就显示,完整结果继续补齐。
- 纯空白不显示来源胶囊,也不构建来源快照。
空白结果通常非常多,来源细分需要再次遍历对象和关联字段,会把“首屏快”重新拖慢。
交互策略
当前采用类似 stale-while-revalidate 的交互:用户输入立即更新;新搜索开始时不清空旧结果;顶部显示轻量进度;同一批结果准备好后一次性替换。
这比“先清空列表,再显示 loading,再填结果”稳定很多,尤其适合空格和大结果搜索。
SwiftUI 与 UIKit 边界
搜索慢的核心不是 SwiftUI 或 UIKit,而是 Core Data 大范围查询、关联谓词、大结果对象解析、来源快照遍历,以及 SwiftUI List 在结果大规模变化时的 diff 和重建。
UIKit 列表可以更细粒度控制 reload,可能减少部分视觉闪烁;但不能解决数据库查询和结果分类本身的耗时。当前路线是保留 UIKit 搜索框,SwiftUI 列表通过旧结果保留、少刷新、首屏限制和纯空白 fast path 降低体感卡顿。
后续方向
- 真分页:不要自动补全全部结果,滚动或显示更多时再按分类追加。
- 搜索索引:维护轻量 SearchIndex,把标题、备注、标签名、待办名预展开。
- 来源计数后台化:普通关键词也可先显示 All 结果,再异步补来源计数。
- 结果稳定性:快速输入时保留旧结果,但标题处显示“正在更新”。