news 2026/4/27 6:30:26

我用 SwiftData 做了一个订阅管理 App,把每月「订阅刺客」揪出来

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
我用 SwiftData 做了一个订阅管理 App,把每月「订阅刺客」揪出来

这个 App 是怎么来的

说实话,做这个 App 的起点挺俗的——信用卡账单。

某个月翻账单的时候,我发现有一笔扣费完全对不上号,折腾了十几分钟才想起来:三个月前试用了某个 SaaS 工具,试用期结束自动续费了,我压根没注意。金额不大,但那种「钱不知不觉就没了」的感觉很难受。

我去 App Store 找了一圈订阅管理工具,要么太重(预算管理全家桶那种),要么太简陋(就是个备忘录),没有一个让我觉得「就是这个」。

所以就自己做了一个。取名「订阅斩」(SubKiller),核心就一件事:把你每个月到底在订阅上花了多少钱,清清楚楚摆出来,然后让你有仪式感地「斩掉」不需要的那些


产品逻辑:为什么要有「斩」这个动作

普通的订阅管理 App,停用一个订阅就是点「删除」或者「归档」,平淡无奇。

我想了很久,觉得这个动作应该有点重量感。毕竟你在主动切断一笔持续的资金流出,这不是删一条备忘录,这是一个决策。

所以我把「停用订阅」这个操作包装成「斩」——有动画、有成就感、有一个专门的「已斩」Tab 存档你斩过的订阅。你随时可以进去看,「我已经斩掉了 7 个订阅,累计每月节省 ¥318」,这个感觉完全不一样。


数据模型怎么设计的

App 用 SwiftData 做持久化,核心模型是Subscription,记录了名称、金额、计费周期、下次扣费日期、状态(活跃/已斩)、是否共享、退订渠道 URL 等字段。

计费周期用了一个BillingCycle枚举,monthly/quarterly/yearly三种,换算成月均费用用multiplierToMonthly来做:

enumBillingCycle:Int,CaseIterable,Identifiable{casemonthly=0casequarterly=1caseyearly=2varmultiplierToMonthly:Double{switchself{case.monthly:1case.quarterly:1.0/3.0case.yearly:1.0/12.0}}}``` 这样不管用户录入的是年费还是季费,首页仪表盘展示「本月预计支出」的时候都能统一折算。---## 风险检测:这个功能是后来加的 最开始版本没有风险检测,只是纯粹记录和展示。 但我发现光「记录」还不够用。用户把订阅都录进去了,但每个月也不一定会主动去翻。有些订阅放在那里,半年没用过,但因为不显眼,就一直续费。 所以加了一个「风险等级检测」机制。判断逻辑是基于App里能实际感知到的信号:-**高危**:`isTrial==true` 的试用期订阅、年费类订阅且距下次扣费 ≤7天且金额 ≥ ¥100--**中危**:`nextBillingDate` 距今 ≤14天,且该订阅自录入以来从未被手动「确认使用」过(即 `lastConfirmedDate` 为nil或距今超过30天) 换句话说,我能感知的信号只有两类:一是订阅本身的元数据(金额、周期、下次扣费日),二是用户在App内的主动操作(有没有点过「我在用这个」)。做不到检测用户有没有真的打开某个App,所以高危判断完全基于账单维度,不靠猜。 高危订阅会在首页仪表盘有明显提示,用户能一眼看到「你有2个高危订阅需要审查」,点进去就能直接斩。我自己用了两周,斩掉了4个订阅,一个是快忘了的设计工具年费,一个是根本没在用的云存储,每月节省将近 ¥200---##SwiftData迁移:这个坑必须说一下 `AppSettings` 里有几个字段是后来加的,比如 `remindBefore7Days`、`iCloudSyncEnabled`。如果直接往 `@Model` 里加字段,旧版本用户升级之后SwiftData找不到对应列,轻则数据读不出来,重则直接崩。 我试了三个方案,前两个(手动 `ModelMigrationPlan`、用 `@Transient` 绕过)各有问题,最后用的是「Optional字段+originalName 标注+计算属性兜底」这个组合: ```swift@ModelfinalclassAppSettings{// 用 originalName 锁定持久化列名,声明为 Optional 兼容旧 schema@Attribute(originalName:"remindersEnabled")varremindersEnabledStorage:Bool?@Attribute(originalName:"iCloudSyncEnabled")variCloudSyncEnabledStorage:Bool?// 对外暴露的计算属性,提供合理默认值varremindersEnabled:Bool{get{remindersEnabledStorage??true}set{remindersEnabledStorage=newValue}}variCloudSyncEnabled:Bool{get{iCloudSyncEnabledStorage??true}set{iCloudSyncEnabledStorage=newValue}}}``` `@Attribute(originalName:)` 的作用是告诉SwiftData「持久化层的列名是这个,但Swift属性名我可以随便改」,配合 `Optional` 声明,旧数据库里没有这列时不会崩,直接返回nil,然后计算属性给一个合理默认值。现在凡是新加字段,我习惯性先想「旧数据怎么办」,这个三件套基本能兜住大部分情况。---## 订阅预设库:减少录入摩擦 录入订阅是最高频的操作,如果每次都要手动填名字、金额、周期,用两次就放弃了。 所以做了一个 `SubscriptionCatalog`,内置了几十个常见订阅服务的预设,包括NetflixYouTubePremiumAppleTV+Spotify、各种国内流媒体和SaaS工具。用户输入名字时会模糊匹配,命中预设之后自动填入推荐价格、计费周期、退订渠道。 每个预设里还有 `sharedRecommended` 字段,标记这个服务是否适合家庭共享——如果是,会提示用户「这个服务支持多人共享,算下来人均更划算」。---## 当前状态App1.1版本,已上架AppStoreID:6761400615),支持 iOS 和 iPadOS。 上线3周,DAU40左右。目前最大的留存问题很明显:用户录入第一个订阅之后就再也不回来了。首次使用的完整率还行,但次日留存很低。我看来原因是App本身的「被动价值」不够强——不开App也不影响生活,直到下次被订阅刺了一刀才会想起来。 接下来优先做两件事:一是Widget,让用户在桌面直接看到本月订阅支出,制造每日曝光;二是到期提醒通知,在扣费前7天和3天推一条,把用户拉回来做决策。`AppSettings` 里 `remindBefore7Days` 和 `remindBefore3Days` 的字段已经在了,通知调度逻辑下个版本上。 对了,`ocrUsageCount` 字段也已经在数据模型里占位——用OCR识别账单截图自动提取订阅信息,这个功能打算在1.3做,相关使用限额逻辑也预留了,到时候单独写一篇。---## 最后 你现在每个月在订阅上花了多少钱?这个数字大部分人真的说不出来,不妨认真算一次,可能会吓到自己。 如果你也在用SwiftData做 iOS 项目,尤其是踩过 schema 迁移的坑,欢迎在评论区聊——这块官方文档写得很薄,踩坑经验互换比自己摸索快多了。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/27 6:29:20

PHP自动化重构:RectorPHP代码升级与重构终极指南

PHP自动化重构:RectorPHP代码升级与重构终极指南 【免费下载链接】awesome-php A curated list of amazingly awesome PHP libraries, resources and shiny things. 项目地址: https://gitcode.com/gh_mirrors/aw/awesome-php PHP自动化重构是提升代码质量和…

作者头像 李华
网站建设 2026/4/27 6:28:58

【花雕学编程】Arduino BLDC 之超声波矩阵动态差速跟随机器人

基于 Arduino 平台结合 BLDC(无刷直流电机)的超声波矩阵动态差速跟随机器人,是一种利用多传感器阵列构建空间感知场,并通过差速底盘实现高机动性目标追踪的智能系统。与传统的单点跟随不同,该系统通过“矩阵化”的感知…

作者头像 李华
网站建设 2026/4/27 6:28:48

机器学习自由度:概念解析与模型优化实践

1. 机器学习中的自由度概念解析在统计建模和机器学习领域,自由度(Degrees of Freedom)是一个既基础又容易被忽视的核心概念。我第一次深入理解这个概念是在调试线性回归模型时,发现训练误差和测试误差出现异常差异的时候。自由度本…

作者头像 李华
网站建设 2026/4/27 6:24:31

如何从其他语言调用jq:跨语言使用JSON处理工具的终极指南

如何从其他语言调用jq:跨语言使用JSON处理工具的终极指南 【免费下载链接】jq Command-line JSON processor 项目地址: https://gitcode.com/GitHub_Trending/jq/jq jq是一款功能强大的命令行JSON处理器,它允许用户通过简洁的查询语言来过滤、转换…

作者头像 李华
网站建设 2026/4/27 6:20:32

终极Composer指南:PHP依赖管理革命的最佳实践与高级技巧

终极Composer指南:PHP依赖管理革命的最佳实践与高级技巧 【免费下载链接】awesome-php A curated list of amazingly awesome PHP libraries, resources and shiny things. 项目地址: https://gitcode.com/gh_mirrors/aw/awesome-php 在PHP开发中&#xff0c…

作者头像 李华
网站建设 2026/4/27 6:18:28

Awesome PHP国际化解决方案:10个最佳实践打造全球应用

Awesome PHP国际化解决方案:10个最佳实践打造全球应用 【免费下载链接】awesome-php A curated list of amazingly awesome PHP libraries, resources and shiny things. 项目地址: https://gitcode.com/gh_mirrors/aw/awesome-php 在全球化时代,…

作者头像 李华