iOS 独立开发实践:用打卡热力图和规则引擎实现订阅费用的理性裁剪
背景:一个真实的个人痛点
作为独立开发者,我日常会订阅大量线上服务——云存储、AI 工具、流媒体、音乐平台等等。某天翻看信用卡账单时发现,零散的订阅费加起来已经逼近 400 元/月,而其中相当一部分服务的实际使用频率极低。
这并非个例。订阅经济的设计本质就是利用用户的「遗忘成本」——金额小到不值得专门取消,但累积起来相当可观。于是我决定做一个工具来解决这个问题,也就是后来的「订阅斩」。
核心设计思路
「订阅斩」要解决的本质问题是:如何用数据代替直觉,判断一笔订阅值不值得续费?
我拆解为三个子问题:
- 数据采集:如何低成本地获取每条订阅的真实使用频率?
- 可视化呈现:如何让「僵尸订阅」一目了然?
- 决策辅助:如何给出可操作的建议而非简单罗列?
1. 轻量打卡机制
最初考虑过自动化方案(读取屏幕使用时间、Hook 应用启动等),但受限于 iOS 沙箱机制和隐私策略,最终选择了最朴素的路径:每日 5 秒手动打卡。
用户每天只需回答「昨天是否使用了这个服务」,是/否二选一。这个设计的关键在于把认知负担降到最低——不需要记录时长,不需要分类,只需要一个布尔值。
实现上用了 UserDefaults + 本地日历模型,30 天为一个滚动窗口。
2. 热力图可视化
30 天的打卡数据以热力图(Heatmap)形式呈现。灵感来自 GitHub 的 contribution graph,但维度更简单——每个订阅一行,每天一格,填充色代表是否使用。
当多条订阅并排展示时,哪些行几乎全灰(未使用)、哪些行密密麻麻(高频使用),对比极为直观。这比任何数字指标都更有说服力。
技术上使用 SwiftUI 的 LazyVGrid 实现,性能在 20+ 条订阅时依然流畅。
3. 砍单顾问:基于规则的决策引擎
v1.6 版本新增的「砍单顾问」并非机器学习模型,而是一套简洁的规则引擎,核心逻辑如下:
let usageRate = daysUsed / 30.0
let dailyCost = monthlyCost / 30.0
switch (usageRate, dailyCost) {
case (let r, _) where r < 0.1:
return .killImmediately // 使用率 < 10%,立即斩杀
case (let r, let c) where r < 0.3 && c > 1.0:
return .considerSeriously // 使用率 < 30% 且日均成本 > 1元,认真考虑
default:
return .keepRecommended // 其余建议保留
}
输出三档建议:立即斩杀 / 认真考虑 / 建议保留。
之所以不用更复杂的算法,是因为这个场景下用户需要的是「可解释的理由」而非「黑箱评分」。规则引擎的每条输出都可以附带清晰的归因文案(如「30 天内仅使用 2 天,使用率 6.7%」),用户信任度更高。
一些值得展开的技术细节
OCR 识别扣费记录
手动录入订阅信息是最大的摩擦点。为降低上手门槛,我集成了 Vision 框架的文字识别能力,支持用户直接截图微信/支付宝的扣费通知,自动提取服务名称和金额。
难点在于不同支付平台的模板差异较大,我通过正则匹配 + 关键词定位的组合策略来处理,识别率大约在 85% 左右,未识别的部分回退到手动确认。
日均成本换算
一个看似简单但体验影响很大的设计:把所有订阅统一换算为「每天花多少钱」。
月费 25 元看起来无所谓,但显示为「0.83 元/天」时,用户会不自觉地和其他日常消费比较(一瓶水、一次公交)。这种锚定效应对决策有明显推动作用。年费订阅同理,除以 365 后很多看似划算的年费也会暴露出使用率不足的问题。
纯本地架构
整个 App 没有任何网络请求,不需要注册登录,数据全部存在本地(Core Data)。这既是隐私承诺,也是架构选择——对于工具类 App,无后端意味着零运维成本,对独立开发者而言这非常重要。
实际效果
我自己用砍单顾问跑完一轮后,最终砍掉了 5 个订阅,月省 168 元,年化节省超过 2000 元。被砍掉的订阅会进入一个「墓园」界面,累计节省金额实时滚动显示——这个 gamification 设计意外地提升了用户粘性。
小结
「订阅斩」的技术栈并不复杂(SwiftUI + Core Data + Vision),真正的价值在于把一个模糊的财务焦虑转化为可量化、可操作的决策流程。
作为独立开发者,我越来越相信:好的工具不一定需要复杂的技术,但一定需要对问题的精确拆解。这个 App 不会让人变有钱,但能让每一笔订阅支出都经过理性审视。
如果你也有类似的订阅管理困扰,或者对规则引擎在消费决策场景的应用感兴趣,欢迎交流。

浙公网安备 33010602011771号