edit | blame | history | raw

TagLauncher 新版架构方案 V1

生成日期:2026-05-20

1. 设计目标

如果从零开始重建 TagLauncher,新架构要同时满足五个目标:

  1. 完整实现已经确认的产品功能和业务需求。
  2. 向下兼容到 macOS Catalina,也就是 Intel 老 Mac 时代。
  3. 主界面打开、滚动、搜索、拖拽都要明显更丝滑。
  4. 代码结构要长期可维护,后续新增功能不需要继续堆在一个大视图里。
  5. 用户数据要安全、可备份、可恢复、可导出到另一台 Mac。

2. 总体判断

新版不建议继续用重 SwiftUI 作为主界面骨架。

原因不是 SwiftUI 不好,而是目标里明确有 macOS Catalina 和老 Intel Mac。Catalina 时代的 SwiftUI 能力、性能和兼容性都不够稳定,尤其是:

  • 大量 App 图标网格。
  • hover 高频交互。
  • 拖拽和命中检测。
  • 多窗口层级。
  • 快速搜索输入焦点。
  • 设置窗口和覆盖层共存。

更稳的方案是:

AppKit-first + 纯 Swift 领域核心 + SQLite 本地存储 + 预计算搜索索引 + 虚拟化 App 网格

SwiftUI 可以保留为小范围可选能力,但不作为主界面和核心交互的基础。

3. 架构原则

3.1 用户体验优先

架构按用户任务拆分,而不是按技术控件拆分。

核心用户任务:

  • 打开。
  • 浏览。
  • 搜索。
  • 启动。
  • 整理。
  • 备注。
  • 备份。

3.2 领域核心和系统适配分离

业务规则不直接写在窗口、ViewController 或 AppKit delegate 里。

例如:

  • App 如何分组,是领域规则。
  • 快捷键如何注册,是系统适配。
  • 搜索如何评分,是搜索领域。
  • 网格如何绘制,是展示层。

3.3 主线程只做界面

主线程只处理:

  • 绘制。
  • 用户事件。
  • 最终 UI 状态提交。

以下工作不在主线程做:

  • App 扫描。
  • 图标读取和缩放。
  • Smart Start 匹配。
  • 搜索索引构建。
  • 数据库读写。
  • 备份清理。

3.4 所有重计算可缓存、可增量、可取消

任何可能超过一帧预算的工作都要满足:

  • 可缓存。
  • 可增量更新。
  • 可取消。
  • 可复用上一次结果。

3.5 Catalina 兼容优先

避免依赖新系统能力:

  • 不用 MenuBarExtra
  • 不依赖现代 SwiftUI。
  • 不把 Swift Concurrency actor 作为硬前提。
  • 不依赖 macOS 13+ API。

采用成熟稳定 API:

  • AppKit。
  • Carbon global hotkey。
  • LaunchAgent。
  • SQLite。
  • DispatchQueue / OperationQueue。
  • NSCollectionView。
  • NSVisualEffectView。
  • NSPanel。

4. 推荐技术栈

方案
UI AppKit
主 App 外壳 NSApplicationDelegate + NSStatusItem
主界面窗口 NSPanel / borderless overlay
App 网格 NSCollectionView + diffable data source
Quick Search 独立 NSPanel / NSViewController / NSTextField / NSTableView 或 NSCollectionView
设置 NSWindowController + AppKit 表单
本地数据库 SQLite + WAL
数据访问 Repository 模式
异步任务 OperationQueue + GCD
搜索索引 内存索引,必要时辅以 SQLite FTS5
图标缓存 内存 LRU + 磁盘缩略图缓存
本地化 Bundle JSON 或 stringsdict,可封装成 LocalizationService
全局快捷键 Carbon RegisterEventHotKey
开机登录 LaunchAgent,必要时未来再封装 SMAppService 分支

5. 总体模块图

flowchart TD
    Shell["App Shell<br/>菜单栏、Dock、快捷键、窗口"]
    MainUI["Main Launcher UI<br/>浏览、标签导航、拖拽整理"]
    SearchUI["Quick Search UI<br/>输入、建议、结果、启动"]
    SettingsUI["Settings UI<br/>语言、外观、数据、快捷键状态"]

    AppCore["Domain Core<br/>App、标签、备注、分类方案、规则"]
    SearchCore["Search Core<br/>索引、匹配、评分、快速建议"]
    SmartCore["Smart Start Core<br/>默认目录、初始分类、默认备注"]
    DataCore["Data Core<br/>SQLite、备份、导入导出、版本管理"]

    SystemAdapters["System Adapters<br/>App 扫描、NSWorkspace、Hotkey、Login Item"]
    ResourceLayer["Resources<br/>本地化、Smart Catalog、图标资源"]
    Infra["Infrastructure<br/>任务调度、日志、缓存、事件总线"]

    Shell --> MainUI
    Shell --> SearchUI
    Shell --> SettingsUI
    MainUI --> AppCore
    SearchUI --> SearchCore
    SettingsUI --> AppCore
    AppCore --> DataCore
    AppCore --> SmartCore
    SearchCore --> AppCore
    SmartCore --> ResourceLayer
    SystemAdapters --> AppCore
    DataCore --> Infra
    SystemAdapters --> Infra

6. 分层设计

6.1 App Shell 层

职责:

  • App 生命周期。
  • 菜单栏图标。
  • Dock 显示策略。
  • 主覆盖层窗口。
  • Quick Search 直接窗口。
  • 设置窗口。
  • 快捷键注册和状态。
  • 开机登录。

建议组件:

AppCoordinator
MenuBarController
HotkeyController
WindowCoordinator
LoginItemController
ApplicationMenuController

设计要点:

  • 所有窗口由 WindowCoordinator 管理,避免窗口层级逻辑散落。
  • 主界面和 Quick Search 可以共享数据上下文,但窗口生命周期独立。
  • Quick Search 直接入口不必先完整渲染主界面,可以直接打开搜索面板。

6.2 Presentation 层

职责:

  • 展示数据。
  • 处理用户事件。
  • 把事件转为 command。

建议组件:

MainLauncherViewController
TagNavigatorViewController
AppGridViewController
AppDetailBubbleController
EditModeController
QuickSearchViewController
SettingsWindowController

设计要点:

  • AppGrid 使用 NSCollectionView,按 section 表示标签分组。
  • 不用每个 App 一个复杂 SwiftUI View,减少 diff 和 layout 成本。
  • hover 效果用 CALayer 和 tracking area,避免频繁触发全树刷新。
  • 拖拽命中由专门 DragCoordinator 管理,不和业务变更耦合。

6.3 Domain Core 层

职责:

  • 表达产品业务对象。
  • 执行业务规则。
  • 不依赖 AppKit。

建议模块:

AppLibrary
Tagging
Notes
UsageHistory
CategoryScheme
UncommonApp
LocalizationDomain

核心对象:

AppRecord
Tag
TagAssignment
AppNote
UsageStat
CategoryScheme
UncommonMarker
LaunchResult

设计要点:

  • Domain Core 只认识数据和业务动作,不认识按钮、窗口、颜色控件。
  • 所有业务变更通过 command 进入,例如 AssignTagsCommandMoveAppCommandEditNoteCommand
  • command 执行后产生 domain event,例如 TagsChangedNoteChangedAppLaunched

6.4 Search Core 层

职责:

  • 构建搜索文档。
  • 快速建议。
  • 查询匹配。
  • 排序。

建议组件:

SearchIndexBuilder
SearchIndex
QueryNormalizer
MatchEngine
Ranker
QuickSuggestionsProvider

设计要点:

  • 搜索索引是内存结构,从 AppLibrary 快照构建。
  • App 数据变化后增量更新索引,而不是每次输入重新构建。
  • 查询输入只走轻量匹配和排序。
  • 空查询直接走 Quick Suggestions,不走完整搜索。
  • 搜索结果只返回轻量 view model,图标由图标缓存异步补齐。

6.5 Smart Start Core 层

职责:

  • 读取内置默认目录。
  • 匹配本机 App。
  • 生成初始分类 draft。
  • 应用或建议分类方案。
  • 管理默认备注。

建议组件:

SmartCatalogProvider
SmartMatcher
SmartDraftBuilder
SmartApplyService
DefaultNoteLocalizer

设计要点:

  • Smart Start 是纯本地能力。
  • 目录资源建议构建期编译成 SQLite 或二进制索引资源,而不是运行时解析大 JSON。
  • 匹配索引用 bundle identifier 和 normalizedName 双索引。
  • 默认备注按语言拆分资源,避免一次加载 29 种语言全部备注。

6.6 Data Core 层

职责:

  • 用户数据存储。
  • 事务。
  • 备份。
  • 导入导出。
  • schema 版本管理。

推荐 SQLite,而不是单一 JSON。

原因:

  • 更适合增量读写。
  • 更容易做事务和回滚。
  • 大量 App、标签、备注和长期使用数据下更稳定。
  • 可以安全支持 WAL。
  • 未来增加搜索、统计和版本升级更容易。

建议表:

含义
apps 已知 App 基础身份缓存
tags 标签定义
app_tags App 与标签多对多关系
notes App 备注
usage_stats 打开次数和最近打开
uncommon_markers 不常用标记和来源
category_schemes 分类方案元信息
scheme_backups 备份索引
disabled_system_categories 用户删除过的系统分类
settings_shadow 需要随导出包携带的业务设置
smart_start_state Smart Start 运行状态

设计要点:

  • 用户操作以事务提交。
  • 每次重大分类方案变更前创建快照。
  • 导入导出用公开 JSON 包格式,不直接暴露 SQLite 内部格式。
  • SQLite 是内部存储,导出的备份文件是稳定用户协议。

6.7 System Adapters 层

职责:

  • 屏蔽 macOS API 差异。

建议组件:

AppDiscoveryAdapter
IconProvider
LaunchServicesAdapter
HotkeyAdapter
LoginItemAdapter
FilePanelAdapter
ScreenAdapter

设计要点:

  • AppDiscovery 只负责发现事实,不写业务数据库。
  • LaunchServices 只负责启动并返回结果,不记录历史。
  • HotkeyAdapter 只负责注册、注销、失败状态,不决定产品文案。
  • LoginItemAdapter 内部按系统版本选择实现。

7. 数据模型建议

7.1 AppRecord

AppRecord
├── appID
├── displayName
├── localizedNames
├── bundleIdentifier
├── path
├── isAppleApp
├── sourceLocation
├── iconCacheKey
└── lastSeenAt

设计规则:

  • appID 是内部稳定 ID,不直接等于 path。
  • path 可以变,bundle identifier 可以缺失,所以需要身份解析层。
  • 用户数据关联到 appID,导入导出时可附带 bundleIdentifier 和 path 作为恢复线索。

7.2 Tag

Tag
├── tagID
├── displayName
├── color
├── sortOrder
├── systemCategoryID
└── isUserDeletedSystemTag

设计规则:

  • 用户标签和系统标签统一为 Tag。
  • 系统标签通过 systemCategoryID 保持稳定身份。
  • 显示名可以随语言变化,但系统身份不变。

7.3 AppNote

AppNote
├── appID
├── text
├── source
├── languageCode
└── updatedAt

备注来源:

  • 用户手写。
  • Smart Start 默认。
  • Apple 默认。

设计规则:

  • 用户手写永远优先。
  • 默认备注可以随语言重本地化。
  • 备注长度限制在领域层统一执行。

7.4 CategoryScheme

CategoryScheme
├── schemeID
├── name
├── createdAt
├── changedAt
├── origin
└── previousSchemeID

设计规则:

  • 分类方案是用户数据的一等对象。
  • 备份不只是文件路径,而是可展示、可恢复的方案历史。
  • UI 只显示当前方案和上一个方案,但底层可以保留更多历史。

8. 关键流程设计

8.1 启动流程

启动 App
-> 初始化数据库和配置
-> 加载用户设置
-> 注册菜单栏和快捷键
-> 后台预热 AppLibrary 快照
-> 后台预热 SearchIndex
-> 用户触发主界面或 Quick Search 时直接使用最近快照

体验收益:

  • 打开主界面不被完整扫描阻塞。
  • Quick Search 可以先显示最近缓存,再增量刷新。

8.2 App 扫描流程

扫描标准目录
-> 生成 AppDiscoverySnapshot
-> 身份解析与去重
-> 增量写入 apps 表
-> 对新增/移除 App 发出事件
-> AppLibrary 生成新快照
-> UI 和 SearchIndex 增量更新

设计规则:

  • 扫描是事实更新,不直接修改用户分类。
  • 新增 App 的“不常用”或默认备注由领域服务订阅事件后处理。
  • 曾经扫描到的 App 在某次扫描中消失时,不立即删除用户数据,只标记 lastSeen 状态,避免外接盘或临时路径导致数据丢失。

8.3 主界面打开流程

用户触发主界面
-> WindowCoordinator 显示 overlay
-> MainLauncherViewController 使用 AppLibrary 当前快照
-> AppGrid diff 更新
-> 后台检查是否需要刷新扫描

设计规则:

  • 显示窗口不等待扫描。
  • 扫描完成后用 diff 平滑更新。
  • 图标可先显示缓存缩略图,再异步刷新高清图。

8.4 Quick Search 流程

用户触发 Quick Search
-> 立即打开搜索面板
-> 输入框获得焦点
-> 空查询显示 Quick Suggestions
-> 用户输入
-> SearchIndex 同步返回轻量结果
-> 图标和备注异步补齐
-> Enter 启动

设计规则:

  • 搜索输入路径不能访问磁盘。
  • 搜索输入路径不能读取 Bundle。
  • 搜索输入路径不能触发 App 扫描。
  • 每次输入只处理内存索引。

8.5 标签整理流程

用户添加/移除/拖拽标签
-> UI 产生 command
-> Domain 校验规则
-> DataCore 在事务中写入
-> 产生 domain event
-> AppLibrary 快照更新
-> Main UI diff 更新
-> SearchIndex 增量更新标签字段

设计规则:

  • UI 不直接改数据库。
  • 业务失败返回明确错误。
  • 分类方案变更自动进入备份批次。

8.6 Smart Start 流程

AppLibrary 快照就绪
-> SmartStartPolicy 判断是否需要运行
-> SmartMatcher 生成 draft
-> 新用户自动应用
-> 老用户显示建议
-> 应用前创建 scheme snapshot
-> 写入标签和默认备注

设计规则:

  • Smart Start 不阻塞主界面出现。
  • Smart Start 应用完成后以通知形式告诉用户结果。
  • 默认备注和用户备注分层保存,不混成不可区分文本。

9. 丝滑体验专项设计

9.1 主界面

采用:

  • NSCollectionView 虚拟化。
  • Section-based data source。
  • Cell reuse。
  • Diffable update。
  • 图标异步缓存。
  • 分组布局缓存。

避免:

  • 每个 hover 都触发大范围状态刷新。
  • 滚动时计算全局坐标。
  • App 图标每次 update 都重绘。
  • 搜索打开时刷新全量 App。

9.2 Quick Search

采用:

  • 常驻预热搜索索引。
  • 查询路径纯内存。
  • 输入防抖只用于昂贵辅助结果,不用于核心匹配。
  • 最多展示有限结果。
  • 空查询直接读 UsageStats 排名。

目标:

  • 打开面板小于 50ms。
  • 首字符结果小于 30ms。
  • 连续输入不卡顿。

9.3 拖拽

采用:

  • 独立 DragCoordinator。
  • 拖拽图层使用 CALayer。
  • 命中目标预注册。
  • 拖拽期间冻结 hover 和气泡。

目标:

  • 拖拽时不触发网格重排。
  • 拖拽视觉和数据提交分离。

9.4 图标

采用三级缓存:

  1. 内存 LRU:当前会话最快。
  2. 磁盘缩略图:跨启动复用。
  3. NSWorkspace 原始图标:兜底。

设计规则:

  • 列表只显示目标尺寸缩略图,不在 cell 中实时缩放大图。
  • 后台生成多尺寸缩略图,例如 40、56、80、96。
  • App path 或 bundle version 改变后失效缓存。

9.5 数据写入

采用:

  • SQLite WAL。
  • 批量事务。
  • 写入队列串行化。
  • UI 乐观更新,失败再回滚提示。

目标:

  • 用户拖拽、排序、批量操作时不被磁盘写入卡住。

10. Catalina 与 Intel 兼容策略

10.1 Deployment Target

建议:

macOS 10.15 Catalina
Universal binary: x86_64 + arm64

10.2 避免的新 API

  • 不依赖 SwiftUI App lifecycle。
  • 不依赖 MenuBarExtra。
  • 不依赖 SMAppService 作为唯一开机登录方案。
  • 不依赖 macOS 13 之后的 Window API。
  • 不依赖只能新系统稳定运行的 SwiftUI Grid。

10.3 可用稳定 API

  • NSStatusItem
  • NSPanel
  • NSWindowController
  • NSViewController
  • NSCollectionView
  • NSTableView
  • NSVisualEffectView
  • Carbon RegisterEventHotKey
  • NSWorkspace
  • LaunchAgent
  • SQLite

10.4 构建策略

  • 使用较新 Xcode 构建,但 deployment target 设为 10.15。
  • 编译期用 availability 包住新系统增强。
  • 老系统走保守路径,新系统可开启增强路径。

11. 推荐工程目录

TagLauncher/
├── App/
│   ├── AppCoordinator.swift
│   ├── MenuBarController.swift
│   ├── WindowCoordinator.swift
│   └── HotkeyController.swift
├── Presentation/
│   ├── MainLauncher/
│   ├── QuickSearch/
│   ├── Settings/
│   └── SharedViews/
├── Domain/
│   ├── AppLibrary/
│   ├── Tagging/
│   ├── Notes/
│   ├── Search/
│   ├── SmartStart/
│   └── CategoryScheme/
├── Data/
│   ├── Database/
│   ├── Repositories/
│   ├── Backup/
│   └── ImportExport/
├── SystemAdapters/
│   ├── AppDiscovery/
│   ├── IconProvider/
│   ├── Launcher/
│   ├── LoginItem/
│   └── Hotkeys/
├── Resources/
│   ├── Localization/
│   ├── SmartCatalog/
│   └── Assets/
└── Tests/
    ├── DomainTests/
    ├── SearchTests/
    ├── SmartStartTests/
    └── SchemaVersionTests/

12. 测试策略

12.1 领域测试

必须覆盖:

  • App 分组规则。
  • 标签添加、移除、移动、复制。
  • 未分类确认后的结果。
  • 系统标签删除和恢复。
  • 不常用规则。
  • 备注覆盖优先级。
  • Smart Start 新用户和老用户路径。

12.2 搜索测试

必须覆盖:

  • 空查询快速建议。
  • App 名称匹配。
  • 本地化名称匹配。
  • 标签匹配。
  • 备注匹配。
  • 多词 AND。
  • 精确、前缀、子串、缩写、非连续。
  • 排序稳定性。

12.3 性能测试

建议设定基准:

  • 1000、3000、6000 个 App-like records。
  • Quick Search 首字符耗时。
  • 主界面打开耗时。
  • 滚动帧率。
  • 图标缓存命中和未命中耗时。

12.4 兼容测试

最低覆盖:

  • macOS 10.15 Intel。
  • macOS 11 Intel。
  • macOS 12 Intel。
  • macOS 13 Apple Silicon。
  • macOS 14/15 Apple Silicon。

13. 绿地架构关键选择

设计主题 V1 选择
主 UI 技术 AppKit-first,保证 Catalina 和 Intel Mac 稳定性
App 网格 NSCollectionView 虚拟化和 cell reuse
业务规则 Domain Core 独立,UI 只发送 command
搜索 SearchIndex 常驻,输入路径纯内存
用户数据 SQLite + 事务 + WAL
App 身份 内部 appID + bundleIdentifier/path/name 多线索解析
备注 Note source 显式建模,用户备注优先
Smart Start 资源 默认目录和默认备注资源化,备注按语言拆分加载
兼容性 macOS 10.15 deployment target,x86_64 + arm64 universal binary

14. 分阶段落地建议

Phase 1:领域核心和数据层

  • 定义 Domain Core。
  • 建 SQLite schema。
  • 完成导入导出包格式。
  • 完成 AppLibrary 快照。
  • 完成搜索核心测试。

Phase 2:AppKit 主界面原型

  • NSPanel 主窗口。
  • NSCollectionView App 网格。
  • 标签导航。
  • App 启动。
  • 图标缓存。

Phase 3:Quick Search

  • 独立搜索面板。
  • 内存搜索索引。
  • 快速建议。
  • 键盘启动。

Phase 4:整理能力

  • 标签编辑。
  • 批量编辑。
  • 拖拽移动和复制。
  • 未分类确认。

Phase 5:Smart Start 与多语言

  • 本地目录资源。
  • 初始分类。
  • 默认备注。
  • 语言切换和默认备注重本地化。

Phase 6:设置、备份和兼容性打磨

  • 设置窗口。
  • 导入导出。
  • 上一个方案恢复。
  • 老系统专项测试。
  • 性能基准和回归测试。

15. 结论

在完全从零开始、没有任何历史包袱的前提下,最合理的新版 TagLauncher 架构是一个 AppKit-first 的本地原生架构:

AppKit 稳定交互
+ 纯 Swift 领域核心
+ SQLite 本地数据
+ 预热搜索索引
+ 虚拟化 App 网格
+ 系统 API 适配层

这套方案的核心价值是:

  • 老 Mac 能跑。
  • 新 Mac 更顺。
  • 业务规则可测试。
  • UI 不背业务债。
  • 搜索和浏览互不拖累。
  • 用户数据更安全。

这就是我建议的 TagLauncher 新版架构方案 V1。