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 降低体感卡顿。

后续方向

  1. 真分页:不要自动补全全部结果,滚动或显示更多时再按分类追加。
  2. 搜索索引:维护轻量 SearchIndex,把标题、备注、标签名、待办名预展开。
  3. 来源计数后台化:普通关键词也可先显示 All 结果,再异步补来源计数。
  4. 结果稳定性:快速输入时保留旧结果,但标题处显示“正在更新”。