AlarmKit 开发手记:做最早一批 iOS 26 系统闹钟 App 踩过的坑
WWDC 上 AlarmKit 一公布,我当周就开工了——「会议闹钟」这个想法在我的备忘录里躺了一年,等的就是这个 API。起步早意味着 MeetAlarm 成了 App Store 上最早一批 AlarmKit App,也意味着我在文档还很薄的阶段攒了一身伤疤。这篇是我希望自己第一天就能读到的笔记。没有什么秘密,只是那些非得把 App 发出去才能真正内化的东西。
心智模型:你负责声明,系统负责响
关于 AlarmKit,最重要的一点是:响铃那一刻,你的 App 并不在运行。系统不会在响铃时回调你、让你现算点什么。你要做的是提前交给系统一份完整、自洽的声明——什么时候响、全屏页面显示什么、按钮是什么、放什么声音——之后的一切归系统所有。与其说是定时器,不如说更像提交一张表单。
想通这一点,很多设计问题会自己有答案。所有动态的东西——「哪个入会链接是最新的」「现在路况如何」——都必须在排闹钟时算好,而不是响铃时。世界在你排完之后变了?唯一的手段就是取消重排。这直接引出架构上最大的推论:
你真正的工作是「对账」
对一个日历驱动的 App 来说,排闹钟本身大概只占十分之一的代码量。真正的活是对账(reconciliation):每次 App 获得运行机会,就把日历的当前事实和你之前排下的闹钟做一次 diff——会议改期了、取消了、新加了、规则变了——然后把两边收敛到一致。这个过程必须幂等,因为它的执行频率会远超你的想象;还要记日志,因为「为什么没响」和「为什么响了两次」是仅有的两种真正要命的用户反馈。
MeetAlarm 只维护一个滚动的 7 天窗口,每次回到前台加周期性后台刷新时对账。窗口开这么小是刻意的——往后排几个月只会成倍放大「陈旧闹钟」的风险,对用户毫无收益,反正窗口会不断续上。
真金白银换来的坑
- 闹钟授权是独立于通知的一种权限。用户从没见过这个弹窗,有些人会条件反射地拒绝。在触发系统弹窗之前,先用一屏大白话解释为什么需要它——加了引导页之后,我的授权通过率明显上升。
- 日历类 App 权限是叠着来的:日历权限 + 闹钟权限。首次启动连弹两个冷冰冰的系统对话框观感很差,把它们拆开、各配上下文再依次请求。
- 响铃页上的按钮走的是 App Intents,不是任意代码。「一键入会」的真实链路是:闹钟按钮 → intent → 拉起 App → 你自己的路由深链进对应会议。这条链在冷启动下能不能活下来,值得单独安排一轮测试。
- 把响铃 UI 当成「排闹钟那一刻就冻结的静态内容」来设计:标题、按钮、铃声都假定不可再变,文案要保证即使会议在你上次同步后挪了位置也不至于说错话。
- 给自己留一条「几秒后就响」的开发者通道,而且要在真机上跑。靠等真实会议来测闹钟是酷刑,而模拟器在响铃行为、铃声、锁屏表现上不会告诉你真话。
- 尽早测那些难看的状态:静音键拨上、睡眠专注、低电量模式、锁屏、两个闹钟同时响。AlarmKit 的卖点恰恰是这些场景,你的 bug 出在这里也就恰恰最丢人。
拿着大喇叭,更要做有礼貌的客人
AlarmKit 交到你手里的,是这个平台上最具打扰性的能力:让手机无视用户亲手选择的静音设置吼出声。框架会管住一部分,剩下的要靠分寸。默认提前量要保守;「跳过这次」「静音整个系列」必须是一眼可见的一步操作;遇到已拒绝的邀请、全天日程这类边界情况,正确的偏向是「拿不准就别响」。早上七点的一次误响,烧掉的信任比一百次正确响铃挣来的还多。
希望框架下一步补上的
上线运行几周后,我的愿望清单:表达力足够跟住日历重复系列的重复规则,免得不停重排;某种条件取消机制,让已删除的会议即使在 App 没机会运行时也不会响;以及响铃页上更丰富的上下文。这些都是一个 v1 框架自然的 v2 方向——而它的 v1,已经解锁了一整类过去根本不可能存在的 App。
如果你也在用 AlarmKit 做东西
真心欢迎交流——发过 AlarmKit App 的人现在还是个很小的圈子。邮箱:[email protected]。如果你只是想让自己的会议像回事儿地响一声,那个 App 现在有了:MeetAlarm,App Store 免费下载,iOS 26+。
