Highlight
FinanceKit 是 iOS 18 引入的新框架,提供了访问 Apple Wallet 中金融数据的标准化 API,将 Apple Card、Apple Savings、Apple Cash 的数据聚合到本地仓库,所有数据处理均在设备端完成,不需要网络连接。
核心内容
做理财类 App 最大的痛点是拿不到数据。各家银行的 API 格式不统一、接入流程各异,很多甚至根本没有开放接口。用户只能手动录入交易,体验很差,数据也容易出错。
FinanceKit 的出现改变了这个局面。它直接从 Apple Wallet 读取金融数据——Apple Card、Apple Savings、Apple Cash 的账户、余额、交易记录,全部聚合在一个本地仓库里。不需要网络,所有数据在设备端处理。你的 App 不需要对接多家金融机构,只需要对接 FinanceKit 一个框架。
数据访问有三种方式,权限由弱到强。最轻量的是 Transaction Picker——一个系统级 UI 组件,用户手动选择要分享的交易,授权是临时的。其次是查询 API,需要用户显式授权,能一次性拉取特定条件的金融数据。最后是长连接查询 API,基于 Swift Async Sequences,持续监听金融数据变化,还能通过 History Token 实现断点续传,App 重启后从上次的位置继续同步。
详细内容
数据可用性检查
调用任何 FinanceKit API 之前,必须先检查 isDataAvailable。返回 false 时如果继续调用,框架会直接终止你的 App——这是强制的,不是建议(05:38)。
import FinanceKit
let available = FinanceStore.isDataAvailable(
.financialData
)
guard available else {
// No meaningful action can be performed
return
}
关键点:
FinanceStore.isDataAvailable(.financialData)是静态方法,不需要实例- 返回
false时绝不能再调用其他 FinanceKit API,否则 App 会被终止 - 这个值在同一个 iOS 版本内不会变化,可以缓存
Transaction Picker
如果你只需要用户挑选几笔交易(比如报销场景),Transaction Picker 是最简单的方案(08:08)。
import SwiftUI
import FinanceKit
import FinanceKitUI
struct TransactionSelector: View {
@State private var selectedItems: [FinanceKit.Transaction] = []
var body: some View {
if FinanceStore.isDataAvailable(.financialData) {
TransactionPicker(selection: $selectedItems) {
Text("Show Transaction Picker")
}
}
}
关键点:
- 需要额外导入
FinanceKitUI selection绑定一个Transaction数组,用户选择后直接填充- 闭包里放触发按钮的 ViewBuilder
- 授权是临时的(ephemeral),Picker 不会记住用户的选择
授权与查询 API
查询 API 需要用户显式授权。先在 Info.plist 添加 NSFinancialDataUsageDescription,然后调用 requestAuthorization()(12:16)。
import FinanceKit
let store = FinanceStore.shared
guard store.isDataAvailable(for: .financialData) else {
return
}
let authStatus = await store.requestAuthorization()
guard authStatus == .authorized else {
return
}
关键点:
requestAuthorization()会弹出系统对话框,用户可以选择分享哪些账户- 用户授权后还可以在设置中随时修改或撤销
- 上架 App Store 需要额外申请 distribution entitlement
查询账户
授权通过后,用 AccountQuery 查询账户(15:24)。
let store = FinanceStore.shared
let sortDescriptor = SortDescriptor(\Account.displayName)
let predicate = #Predicate<Account> { account in
account.institutionName == "Apple"
}
let query = AccountQuery(
sortDescriptors: [sortDescriptor],
predicate: predicate
)
let accounts : [Account] = try await store.accounts(query: query)
关键点:
- 使用 Swift 的
#Predicate宏构建过滤条件 SortDescriptor控制排序,建议至少指定一个- 每种数据类型都有对应的 Query 类
查询余额
余额查询可以限制返回数量,适合绘制趋势图(18:12)。
func getBalances(account: Account) async throws -> [AccountBalance] {
let sortDescriptor = SortDescriptor(\AccountBalance.asOfDate, order: .reverse)
let predicate = #Predicate<AccountBalance> { balance in
balance.available != nil &&
balance.accountId == account.id
}
let query = AccountBalanceQuery(
sortDescriptors: [sortDescriptor],
predicate: predicate,
limit: 7
)
return try await store.accountBalances(query: query).reversed()
}
关键点:
- 按日期倒序取最新的 7 条,再
reversed()恢复时间正序 asOfDate记录余额的计算时间,可用来判断数据新鲜度- 余额也是正数存储,方向靠
creditDebitIndicator区分
长连接查询与断点续传
transactionHistory 返回一个 Async Sequence,持续推送变更(20:27)。
import FinanceKit
let store = FinanceStore.shared
let account: Account = ...
let transactionSequence = store.transactionHistory(
forAccountID: account.id
)
for try await change in transactionSequence {
processChanges(change.inserted, change.updated, change.deleted)
}
用 since 参数传入上次保存的 History Token,就能从断点恢复(21:04)。
import FinanceKit
let store = FinanceStore.shared
let account: Account = ...
let currentToken = loadToken()
let transactionSequence = store.transactionHistory(
forAccountID: account.id,
since: currentToken
)
for try await change in transactionSequence {
processChanges(change.inserted, change.updated, change.deleted)
persist(token: change.newToken)
}
关键点:
- 每次 change 包含
inserted、updated、deleted三个数组 change.newToken是 Codable 的,可以序列化存盘- 传入
since: token后只返回该 token 之后的增量变更 - 如果不需要实时监听,加
isMonitoring: false,处理完已有变更后 Sequence 会自动终止
核心启发
-
做什么:用 Transaction Picker 实现”手动导入交易”功能。为什么值得做:Picker 不需要 Info.plist 描述、不需要授权弹窗、不需要申请 entitlement,接入成本最低。怎么开始:导入
FinanceKitUI,在视图里放一个TransactionPicker,绑定@State数组即可。 -
做什么:用 History Token 实现增量同步。为什么值得做:每次启动都全量拉取所有交易数据,数据量大时既慢又浪费。用 Token 只处理增量,性能提升明显。怎么开始:每次处理完 change 后把
change.newToken持久化到磁盘,下次启动时传给transactionHistory(forAccountID:since:)。 -
做什么:根据账户类型正确解读金额方向。为什么值得做:金额始终是正数,debit/credit 的含义取决于账户类型——资产账户(Apple Cash)的 debit 表示余额减少,负债账户(Apple Card)的 debit 表示可用额度减少。搞混了会让用户看到错误的收支统计。怎么开始:在展示交易列表时,先判断
account是资产还是负债类型,再结合creditDebitIndicator决定显示为收入还是支出。 -
做什么:监听数据限制状态变化。为什么值得做:数据可用性(
isDataAvailable)是常量,但数据限制(restriction)可能在 App 运行期间变化——比如 MDM 策略调整。框架会抛错但不会终止 App,需要自行处理。怎么开始:在 catch 分支中处理限制错误,提示用户当前无法访问金融数据。
关联 Session
- What’s new in Wallet and Apple Pay — Wallet 和 Apple Pay 的最新增强功能
- What’s new in privacy — 新的权限流程与隐私保护机制
- Bring your app’s core features to users with App Intents — App Intents 框架的核心概念
- Track model changes with SwiftData history — 用 History API 追踪数据变更,与 FinanceKit 的 History Token 机制类似
评论
GitHub Issues · utterances