你的时间去了哪里?让数据告诉你答案。
出发点:我不知道我的时间去哪了
每天在电脑前坐 10 小时,晚上回想,脑子里只有两个字:忙。但说不出忙了什么。
写代码是 2 小时还是 5 小时?刷网页是 20 分钟还是 2 小时?微信聊工作断断续续到底多久?——这些问题没有人能回答,包括我自己。人的记忆是出了名的不可靠:今天感受深刻的,未必是时间花得最多的;真正吃掉大块时间的事,反而容易"看不见"。
macOS 自带的"屏幕使用时间"提供了粗糙的分类,但它有几个致命问题:数据不能导出、分类不够细、看不到应用切换的精确时间线。我想知道的不是一个模糊的统计数字,而是精确到秒的时间分配真相。
于是我决定自己做一个。不是去买一个现成的工具,不是用现有的时间追踪 App——那些都需要手动开始/停止计时,坚持不了三天。我要的是一个完全自动、无感的系统:它在后台静默运行,不需要我任何操作,默默记录一切,然后在我需要的时候把数据组织成我能看懂的答案。
思考的结论很简单:注意力是程序员最稀缺的资源,而管理注意力的第一步,是看清它流向哪里。 我需要一面镜子。
dmg体验: https://oss.vizwise.online/Macos/TimeMirror-0.12.1.zip
设计思路:数据分层,从信号到洞察
第一层:传感器层(信号采集)
iScreenMonitor 的根本设计哲学是:数据的源头必须来自系统级信号,而不是人的手动输入。人不可靠——人会忘、会偷懒、会主观扭曲。系统的传感器不会。
我设计了五条数据采集通道:
| 通道 | 采集内容 | 频率 |
|---|---|---|
| 前台应用切换 | 当前激活窗口的应用名和 Bundle ID | 每秒采样,2 秒防抖 |
| 键盘事件 | 每次击键(按输入法分类) | 事件驱动 + 60 秒批量写入 |
| 系统资源采样 | CPU、内存、Swap、负载平均值 | 每 60 秒 |
| 空闲状态检测 | 通过 IOKit HIDIdleTime 微秒级检测用户是否在场 | 持续监测 |
| 系统事件 | 睡眠、锁屏、启动/关机 | 事件驱动 |
这些信号是最原始的物理事实——不经过人的判断,不经过任何算法加工。它们构成了整座数据大厦的地基。
第二层:会话层(状态机编排)
原始信号是离散的、碎片化的。一条应用切换事件只是一瞬间的快照,"Chrome 从 9:05 到 9:23"这个有意义的信息,必须由状态机来合成。
MonitorEngine 的核心是一个两态注意力状态机:
active(某个App) ←→ inactive(离开/锁屏/睡眠) 每秒执行一次 attentionTick(),遵循经典的 Snapshot → Reduce → Transition 模式:
- Snapshot:采集当前世界状态(前台应用、锁屏状态、睡眠状态)
- Reduce:纯函数状态转换——同一应用继续、不同应用等待 2 秒防抖确认、锁屏/睡眠立即进入 inactive
- Transition:状态发生变化时,在 SQLite 事务中原子性地写入数据库
这个设计让一条条零散的传感器读数,自动拼成了有头有尾的会话(Session)。每一条 session 都有精确的开始时间、结束时间、持续时长和结束原因(正常切换 / 用户离开 / 系统睡眠 / 屏幕锁定 / 引擎重启)。
第三层:统计层(聚合洞察)
Session 是"砖块",Dashboard 上的那些数字才是"房子"。这一层的核心原则是:Dashboard 上展示的所有数据,都是"算出来的",不是"存起来的"。
- "今天 Safari 用了 2.3 小时"——不是存在数据库里的,是
SUM(duration_sec)聚合出来的 - "今天效率集中在上午 10 点"——不是 AI 判断的,是逐条 session 用递归 CTE 切分到小时桶,算活跃占比
- "开发工具占今天时间的 65%"——是 9 类分类器 + GROUP BY 的组合结果
这种"存原始数据、算统计结果"的分层架构,让系统极其灵活:今天我想要按小时看视频分布,明天想要按分类看饼图,都不需要改表结构、不需要重新采集数据。原始数据永远在那里,换一个角度问它问题就行。
数据库设计:由需求长出来的 6 张表
我没有在第一天就画 ER 图设计全部 6 张表。相反,它们是一张一张长出来的:
- 第 1 天:
app_sessions——我就想知道用了什么应用 - 第 3 天:
system_snapshots——我想看 Mac 什么时候变卡 - 第 5 天:
alerts——卡到受不了了,要主动提醒 - 第 7 天:
idle_periods——我需要区分"在用"和"离开了" - 第 10 天:
boot_events——统计重启次数 - 第 14 天:
keystrokes——我想知道自己一天敲了多少键盘
每张表都由一个真实的需求驱动,每张表加出来之后都立即投入使用。这就是渐进式 Schema 生长——不为"将来可能需要"而设计,只为"现在就需要"而演进。
实现方式:AI 驱动的开发模式
iScreenMonitor 的特别之处不在于它用了什么高深的技术——全程 Swift 6.2 + SwiftUI + SQLite,零第三方依赖,纯 macOS 原生 API。
它的特别之处在于:整个项目是用 AI 写出来的。
从一句话到可运行的原型
项目的第一行代码是这样诞生的:我对着 CodeBuddy 说了一句话——
"帮我写一个 macOS 菜单栏 App,检测前台应用切换,记录名称和时间,用 SQLite 存储。"
10 分钟后,代码跑起来了。我第一次看到了真实的数字:Safari 2.3h、CodeBuddy 3.1h、WeChat 1.5h——那一瞬间的感觉很奇妙。像一个近视的人第一次戴上眼镜,世界突然清晰了。
AI 协作的真实面貌
很多人以为"AI 写代码"就是一句话搞定一切。实际远非如此。真实的协作模式是这样的:
我提出需求 → AI 生成代码 → 我运行看结果 → 结果对吗?
对 ✅ → 继续下一个需求
不对 ❌ → 描述现象给 AI → AI 修改 → 再试 这是一个人类主导的迭代循环。AI 是那个打字飞快的助手,但决定做什么、判断对不对、选择走哪条路的,始终是人。
在这个过程中,AI 产生了大量错误,我记录下了最典型的 6 个:
- 忽略 NULL:SUM 空了直接返回 NULL,导致 Dashboard 白屏
- 时区遗漏:SQLite 存的是 UTC 时间戳,AI 直接按北京时间算,凌晨数据全错位
- 忘了去重:统计 App 数量时没用 DISTINCT,数字虚高
- 边界条件:整点切分时间线时,边界上的数据被丢弃
- 过度优化:几百行的小表加了一堆索引,写入速度反而变慢
- SQL 注入:App 名 "O'Reilly" 的单引号直接把 SQL 语法炸了——这是全书最痛的教训
这些错误不是 AI 的缺陷,恰恰相反——它们是最好的老师。每踩一个坑,我就被迫去学背后的原理:NULL 为什么具有"传染性"?时区转换到底发生了什么?B-Tree 索引为什么会减慢写入? 如果 AI 没有犯这些错,我可能永远不会去深入了解这些知识。
我学会的不是"怎么写 SQL",而是三件事
做了这个项目之后,我真正掌握的技能不是手写复杂的递归 CTE(虽然我也学会了)。核心能力只有三样:
- 提出正确的问题——把模糊的需求翻译成计算机能精确执行的查询
- 读懂 AI 的回答——知道它在干什么,而不是盲目复制粘贴
- 验证结果的正确性——用 COUNT(*) 看总量、用 LIMIT 10 看抽样、用 ORDER BY DESC 看极端值、用 IS NULL 看空值、用常识心算验证
我把这称为"验 SQL 五步法"。这不只是技术方法,更是一种思维习惯:永远不信任未经检验的产出,无论是人写的还是 AI 写的。
产品驱动一切
这个项目的核心理念来自一句很朴实的话:你不是在学数据库,你是在做产品。数据库是你路上的工具,不是目的地。
传统的数据库学习路径是:先学理论 → 再学 SQL 语法 → 再做练习题 → 多年后也许用上。而我的路径完全相反:我想做一面镜子 → 我需要存数据 → 我去学怎么存 → 存好了我想查 → 我去学怎么查 → 查的过程中遇到 100 个问题 → 学会了一个大学数据库课程的全部内容。
这不是"学了再做成",而是"为了做成而学"。动力来源不再是"考个好分数",而是"我想看到今天的时间去哪里了"。这个驱动力比任何教科书都强一万倍。
AI 时代的开发者新角色
做完这个项目,我对 AI 时代的开发者角色有了清晰的认知:你不是 SQL 程序员,你是产品经理 + QA。 AI 帮你写代码,问题逼你学原理,产品驱动一切。
你能做的最重要的事,不是记住所有语法,而是知道什么值得做、怎么把需求拆成 AI 能理解的步骤、怎么验证 AI 产出的质量。这些能力,比任何具体的 API 知识都更持久。
最后
iScreenMonitor 每天在我 Mac 上静默运行,像一个忠实的观察者。当我打开 Dashboard,看到那些精确到秒的数字时,我对自己的注意力有了前所未有的清晰认知。而最让我感慨的是:这面镜子本身,就是 AI 帮我打磨出来的。
从一句话到一个完整的产品,从"数据库是什么"到能用递归 CTE 拆分 24 小时时间线,整个过程不过几个星期。AI 让"从想法到产品"的距离变得前所未有的短——但前提是:你确实有一个值得去做的想法。
数据和工具都只是手段,看清自己、改进自己,才是目的。
*XILEJUN · 2026 年 5 月*
作者:xilejun · v1.1 · 2026-05-30
No comments yet