大多数浏览器和
Developer App 均支持流媒体播放。
-
了解 FinanceKit
了解 FinanceKit 如何让你的财务管理 App 在用户同意且拥有控制权的情况下,无缝安全地共享 Apple Cash、Apple Card 等来源的设备端数据。了解如何请求获得账户、交易以及余额的一次性访问权限和持续访问权限,以及如何针对 iOS 和 iPadOS 打造卓越体验。
章节
- 0:00 - Introduction
- 1:48 - Overview of data types
- 5:03 - Access financial data
- 21:56 - Best practices
资源
-
下载
大家好 我叫 Antonio 是 Apple Wallet 团队的 iOS 工程师 理财是现代生活中至关重要的一部分 无论你是想追踪自己的收入 与信用卡支付情况 还是只想了解 自己本月喝卡布奇诺花了多少钱 你都需要及时查看自己的财务数据 而这正是 FinanceKit 的用武之地
FinanceKit API 提供对存储在 Apple 钱包中的 财务数据中央存储库的 访问权限 它访问的所有财务数据都 位于设备本地 无需互联网连接 此外 FinanceKit 还能汇总多个来源的 财务数据 包括 Apple Card 和 Savings 以及 Apple Cash 最后 FinanceKit 还为数据所有者 提供明确的控制权 让他们能够控制 App 可访问的财务数据类型和数据量 从而保护隐私 FinanceKit 可提供 丰富的财务信息 帮助打造极具吸引力的财务 App 包括每个账户的简要详细信息 包括最新已知变动时间在内的 可用余额信息 以及对财务交易的访问权限 如资金进出 或可用信用额度的变化 此外还有所有这些信息的历史记录 我们这就开始吧 今天 我将介绍 FinanceKit 以及关于访问和展示财务数据 需了解的事项 首先 我将简要介绍一下 FinanceKit API 所呈现的核心结构 然后 我将介绍如何使用这个 API 访问存储在 Apple 钱包中的 丰富的财务数据 最后 我将总结一些在将 FinanceKit 整合到 App 时 应谨记的一些最佳实践
现在 我先来介绍 一些可供你使用的核心对象 FinanceKit 提供 3 种主要的 数据类型来进行财务数据建模 最根本的是账户 这可能是支票账户 信用卡 甚至是 Apple Card 储蓄账户 每个账户都有一个余额 简单来说 余额代表着账户 在某一特定时刻的金额 除了余额外 账户还包含交易 简而言之 这是资金进出账户的体现 FinanceKit 会根据 财务数据处理方面的本地法律 跟踪自用户开设账户以来 所有这些模型的变化情况 交易是一个包含 大量有用字段的结构 其中包括一个标识符 它不仅对每笔交易具有唯一性 而且对每台设备也是独一无二的 这个 ID 对于追踪交易 随时间的演变尤其有用 大多数交易都会提供一个 符合 ISO 18245 标准的 商家类别代码 如果财务数据中提供了这个名称 这有助于 App 追踪支出类别 以及商家名称
所有交易都会包含原始交易描述 这是由金融机构提供的 如果可能的话 还会提供一个易于展示的描述 交易始终包含一个交易日期 并且根据机构 提供的信息的状态和深度 FinanceKit 还会提供 交易入账的日期 最后 交易将提供一个金额 由货币代码和十进制值组成 如果交易发生在国外 机构还会提供外币金额 需要注意的是 对于这些金额 FinanceKit 不会进行 货币之间的换算 FinanceKit 提供的数据 与从机构收到的数据完全一样 而且无论是借记还是贷记 均以正十进制值的形式存储 你可能想知道如何确定 给定交易是贷记金额还是借记金额 这时 借贷指示器就派上用场了 每笔交易都有一个 借记或贷记指示器属性 可以通过读取 这个属性来判断资金 是流入还是流出某个账户 毫无疑问 可能的值为 debit 或 credit 但需要注意的是 对这个值的解读会因账户类型而异
如果交易为“debit” 则意味着 Apple Cash 之类的 资产账户的余额减少 或者 如果交易属于 信用卡之类的负债账户 则意味着可用额度减少 当交易的借贷指示器为“credit”时 则意味着交易增加了资产账户的余额 或增加了负债账户的可用额度 以上就是对 FinanceKit 中 会用到的顶层类型的 简单介绍 如需深入了解 请参阅文档 其中介绍了所有可用的类、 结构和字段 但我已经介绍了你需要了解的内容 接下来就进入有趣的部分 也就是使用 FinanceKit API 访问 这些类型所表示的财务数据 访问财务数据主要由 3 个部分组成 包括确定数据可用性、 使用选择器来访问用户选择的 财务数据集 以及使用查询 API 创建对财务数据的 一次性访问或长期访问 让我们先来了解一下 如何确定给定设备是否支持 FinanceKit 如果支持 是否有任何财务数据受到限制 要查看财务数据的可用性 首先需要导入 FinanceKit 然后 使用 FinanceStore 类的 isDataAvailable 方法 并传递 financialData 枚举案例 来查看数据的可用性 如果框架返回 false 则不应进行任何其他 与财务数据相关的调用 在这种情况下 框架会终止 App 以便提供强信号 另一方面 如果返回 true 则可以放心继续调用 因为这个值 不会在 App 的 两次启动之间发生变化 而且可以视为在 iOS 版本中 保持不变
请记住 返回 true 并不保证 在设备上可以访问任何实际数据 即使财务数据可用 对它们的访问也可能在某些设备 和设备配置中受到限制 虽然数据可用性可以被视为常量 但数据限制可能是暂时的 它的值可能会在两次调用之间发生变化 即使你的 App 处于前台也是如此
例如 如果“Apple 钱包”App 不可用 或者公司的移动设备管理系统 限制访问 Apple 钱包
如果数据访问受限 框架会抛出错误 而不终止 App 由此表明数据现在受到限制 此时 你的 App 可以 告知用户当前无法 访问财务数据
确定财务数据可用后 你便可以访问财务数据了 一个简单快捷的方法是 允许使用你的 App 用户 通过 FinanceKit 交易选择器 选择要与你 共享的交易 有了这个新视图 你的 App 可以 决定何时显示可用交易的可选列表 你的 App 用户可以 查看他们的交易列表 并选择希望授予你访问权限的交易 默认情况下 交易按时间顺序排列 但也支持通过在顶部的搜索字段中 输入自由文本 或选择在输入时建议的 一个或多个可用标记 进行筛选 请记住 在交易共享后 你的 App 将立即获得交易访问权限 但对共享交易的访问是短暂的 交易会直接传递到 你的 App 以供即时使用 选择器不会记住或存储任何共享交易 展示选择器非常简单直接 如果你使用过照片选择器 API 便会对交易选择器 API 非常熟悉 使用最简单的方法 导入与 FinanceKit 相关的 UI 框架 FinanceKitUI 然后 声明一个变量 用于保存所选交易 在本示例中 我将使用视图状态属性 正如我前面提到的 你的 App 负责确定 财务数据是否可用 在本示例中 我将在视图主体内 调用 isDataAvailable 然后将选定项目的状态变量作为 第一个参数传递到 TransactionPicker 视图 对于第二个参数 我们将传递一个 ViewBuilder 闭包 在本例中 这个闭包是用于触发 选取器显示的按钮的标签 仅需这几行代码 我就构建了一个按钮 点按这个按钮即会显示交易选择器 并将选定交易提供给我的 App 这就是交易选择器 它非常适合用于追踪 公务差旅费用的 App 或者这样的用例 即 App 用户 可能希望选择性地允许访问特定交易 而不是允许对所有交易 进行无限制访问 甚至包括他们可能认为私密的交易 当然 有些 App 在对钱包中的财务数据 拥有完全访问权限的情况下 可以发挥最大作用
接下来就深入探讨一下可用于获取 对财务数据的完全访问权限的查询 API 这些 API 可以长时间运行 也可仅拉取数据快照 查询 API 是异步的 并提供对 FinanceKit 中 所有可用财务数据类型的 访问权限 而不仅仅是交易数据 需要获得用户同意 以确保 在未获明确许可的情况下不会共享数据 但是 在获得用户同意后 你的 App 便能够建立持久连接 持续访问可用的财务数据 而且 值得高兴的是 这些 API 可用于任何运行 iOS 17.4 或更高版本系统的 iPhone 在 FinanceKit 中 有 2 种查询数据的方法
第一种方法会返回基于当前值的 财务数据类型有序集合 正如你所期望的 它们接受谓词、排序描述符、 限制和偏移参数 并异步返回财务数据类型的 有序集合供你的 App 使用 还有一种仅用于更改的 API 它的工作方式略有不同 这些查询不会返回 匹配的财务数据数组 而是返回所需数据的 更改历史记录 而且 它们可以长期运行 因此 只要机构提供了新数据 你的 App 就能获得 仅有变化的更新 使用这些 API 需要满足一些额外的要求 在深入了解文档之前 你需要确保 交易选择器符合你的需求 首先 你需要在 App 的 info.plist 中添加 一个静态使用说明 这个说明将向顾客显示 为此 你可以在“隐私”下查找 “财务数据使用说明” 或使用 NS Financial Data Usage Description 键手动添加
输入描述后 便可以在代码中 针对 FinanceStore 类 共享实例 调用 requestAuthorization 函数 来请求授权
请求授权会显示一个系统提醒 提示请求访问财务数据的权限 随后会显示一个屏幕 用户可在其中选择要共享的账户 调用 requestAuthorization 的 结果可以是 “granted” 这意味着你有权查询财务数据 但请记住 用户可以随时从“设置”中 撤销这一访问权限 如果访问请求被拒绝 则结果为“denied” 最后 在用户没有机会做出有意义的 选择的情况下 结果也可能会 返回“unknown” 虽然调用 requestAuthorization 会返回一个状态 但 authorizationStatus 函数 只会返回当前状态 而不会提示用户 如果你决定 在 App Store 上发布 App 就需要请求分发授权 我们创建了一个简化的流程 来请求权限 你可以在与本讲座相关的 资源中找到指向 相应信息页面的链接 下面来展示一下 请求授权流程的代码 在导入 FinanceKit 后 我们首先检查财务数据是否可用 并且仅在可用时继续操作 然后 在共享的 FinanceStore 实例上 调用 requestAuthorization 如果适用 这将在 App 顶部 显示系统对话框 用户做出选择后 对话框将关闭 并且异步函数将向 App 返回新的状态
你可以查看用户是否同意 访问他们的财务数据 然后继续操作
如前所述 当你的 App 请求授权 而用户没有表达偏好时 系统会发出提醒 如果用户按下“不允许” 你将收到“denied”授权状态 另一方面 如果用户按下“选择账户” 操作系统将显示 所有符合条件的账户列表 用户可以从中选择想要共享的账户 并为每个账户指明可供你的 App 使用的最早活动 如果用户改变主意 他们可以直接按下“取消” 你的 App 可以 在另一时间再次请求授权
另一方面 用户可以通过按下分享按钮 来最终确定他们的选择 控制权将交还给你的 App
与许多其他框架一样 当用户做出选择时 无论是允许访问还是拒绝访问 请求授权时将不会再次提示用户 但如果他们之后改变主意 可以在“设置”中修改 对他们财务数据的访问权限 他们还可以共享更多账户 甚至更改最早共享日期 你的 App 已请求 访问用户的财务数据并获得准许 接下来探讨一下如何查询这些数据 但首先 我们需要 更深入地了解账户 这是 FinanceKit 公开的 主要财务数据类型之一 让我们来了解一下账户的结构
账户采用枚举用例建模 具有一些所有账户共有的属性 以及其他特定于 某种账户类型的属性 这样一来 FinanceKit 就能灵活地 使用每种账户类型的 特定属性集来建模 同时还能保持一个通用的核心 其中一个通用属性是 本地唯一标识符 账户标识符也出现在 每笔交易和余额中 这样就可以轻松 将每个对象与它们的 所属账户关联起来 每个账户都有一个机构名称 这是一个表示 账户所属机构名称的字符串 还有一个显示名称 即要向顾客显示的账户名称 如果机构支持的话 还会显示一个账户描述 账户的主要货币 用 3 个字母的货币代码表示 这些都是账户模型中的通用属性 如果账户是负债账户 例如 用于进行 Apple Card 账户建模的账户 那么它还会有其他属性 例如 表示可借贷的 最高限额的信用额度 下次付款日期 以及最低支付金额 还可能存在逾期付款金额 现在我们已经了解账户的建模方式 下面我们来查询 FinanceStore 中的一些账户
我们首先定义一个排序描述符 在本例中为账户显示名称 一个查询中可使用 0 个 或多个排序描述符 但我们建议至少使用 1 个 然后 我们使用新的 Swift 谓词宏定义一个谓词 在本例中 我们只需与 Apple Card Cash 和 Savings 相关联的账户
然后 构建查询并传递排序描述符 和谓词作为参数 我们可以看到 每种财务数据类型 都有一个关联的查询 最后 我们可以使用这个查询 从商店中获取匹配的账户 在了解账户之后 接下来 我们来看看可以对余额执行哪些操作 正如我们之前所说 每个账户都至少有一笔余额 并且根据用户 升级到 iOS 17.4 的时间 可能有多个余额
FinanceKit 会保留 余额的历史记录 可用于向用户展示 余额随时间变化的情况 例如 展示趋势 我们先来看看余额的构成 余额以枚举形式建模 并由 3 种情况组成 可用余额 表示在生成余额时 已考虑到待处理的交易 另一方面 已入账余额 表示仅考虑已入账的交易 或者可用和已入账余额 顾名思义 有 2 个字段和 2 个余额
除了这个区别 在所有枚举案例下 余额共享一组属性 与我们今天了解的其他 财务数据类型类似 余额也有一个本地唯一标识符 除了账户标识符 你还可以将余额映射到所属账户 FinanceKit 还会在 AsOfDate 属性中 记录机构计算 余额的日期 这个属性还可用于 检查余额的新鲜度 最后一点也同样重要 余额至少包含一个金额 以十进制和货币代码对的形式表示 此外 余额的金额由机构提供 而不是由框架计算 这些金额以正值存储 框架依赖于另一个 creditDebitIndicator 属性 来区分余额状态 如果资产账户余额的 creditDebitIndicator 为“debit” 则表示余额为负 另一方面 如果余额列为“credit” 则表示余额为负 负债账户的余额 如果拥有贷记指示符 则表示这个账户当时处于贷记状态 但如果账户有已支出的余额 它将会列为“debit” 我们已经了解余额的表现形式 接下来了解一下如何查询数据 以创建类似于此处所示的图表 并绘制出最新可用余额 随日期的变化 在获取余额函数中 我们首先定义 一个反向日期排序描述符 以便从最近的日期开始获取余额 然后 我们将结果 缩小到仅显示可用余额 并且仅限于我们感兴趣的账户余额
现在 构建查询应该不难了 但除了排序描述符和谓词外 我们还要限制结果的数量
然后执行查询 并反转获取的余额 以便按时间顺序显示 我们已经了解了快照查询 下面我们来了解一下长时间运行查询 当你的 App 希望在设备 收到实时更新后 立即使用这些更新 或者希望在 App 启动之间 恢复查询时 就需要使用长时间运行查询 长时间运行查询 基于 Swift 异步序列而构建 可以轻松交付连续的更新流 长时间运行查询不会 像快照查询那样返回模型数组 而是返回变更 变更是与数据库上次状态 或初始状态的差异 变更包括 插入的模型数组 例如 自上次更新以来的所有新交易 更新后的对象数组 例如信用额度增加的负债账户 以及删除的对象标识符 因此你 App 可以删除 钱包中不再存在的 财务数据对象 每次变更还会提供一个历史记录令牌 历史记录令牌是 财务数据存储中特定时间点的代理 它是一个不透明的结构 并且由于它符合 Codable 标准 可以轻松进行序列化 它还可用于恢复长时间运行查询 或在相应时间点之后获取存储更新 出于空间和性能方面的考虑 系统可能会对历史变更进行压缩 这种情况一般比较少见 因为 FinanceKit 会尽量保留 至少几个月的历史记录 但是 如果你的 App 保存的最新令牌 现在指向已压缩的变更 历史记录查询可能会抛出错误 无论如何 这并不是问题 这个框架能够让你的 App 在不丢失任何数据的情况下 重新同步 同时仍然可以恢复查询 让我们来看看如何为交易设置 一个简单的长时间运行查询 导入 FinanceKit 后 使用 transactionHistory API 定义交易历史记录异步序列 然后 在交易序列上 使用 for await 监听所有更改 接下来 我们就可以 随意处理这些更改了 这是一个个人理财 App 示例 它会在打开时载入所有交易 随后 只要我们在关联账户中 获得新的交易 就可以看到它立即显示在屏幕上 我们已经了解了简单的 长时间运行查询的工作方式 接下来我们来使用可恢复功能 首先 每次处理 来自 API 的新更改时 我们都会将历史记录令牌保存在 一个安全的位置 比如磁盘 要使用存储的历史记录令牌 我们必须进行加载 然后 我们可以使用 since 参数 将最近加载的令牌传递给 历史记录查询 通过这一简单的更改 我们的 App 就能在重新启动之间 恢复长时间运行查询 并消除重复事件 财务数据异步序列通常不会终止 因此可以生成实时更新 以供 App 使用 要是可恢复查询 对应用程序而言非常重要 那么最佳解决方案是 向长时间运行查询传递一个参数 使它们不监控新的更改 这样 一旦 App 处理完所有现有变更 异步序列便会终止 这就是确定设备上财务数据的可用性 并进行访问的方法 现在我来总结一下 在将 FinanceKit 整合到 App 时 应谨记的一些最佳实践 重申一下 财务数据 对每个人来说都是非常敏感的信息 因此要尊重用户 在授权你的 App 访问 他们的数据时所表现出的信任 例如 在数据被删除 或用户取消你 App 的 访问权限时 你需要删除数据
即使你可以访问更多数据 也应仅查询所需的数据 交易选择器是一种双赢的 财务数据访问方法 它对你的 App 要求较低 同时仍能提供宝贵的数据 而且用户 可以选择仅共享他们愿意共享的内容 设定财务数据访问权限 旨在提高性能 如果你的 App 可以 访问所有财务数据 那么始终请求 完整的快照似乎会很方便 更高效的策略是 使用可恢复的长时间运行查询 仅处理最新更新 这一策略可显著减少 App 需要处理的数据量 至此 我们已经探讨了 构建你的首款 由 FinanceKit 提供支持的金融 App 所需了解的一切 FinanceKit 使你的 App 能够 访问顾客设备上的财务数据 交易选择器和查询 API 有不同的交互方式和要求 能够支持大量用例 你可以访问 Apple 开发者论坛 在这个平台上 你可以 全年随时提问并获得帮助 最后 如果你有任何反馈意见 我们非常乐意倾听 你可以使用“反馈助理”进行反馈 以上就是全部内容 希望你喜欢这场讲座 感谢大家观看 再见
-
-
5:38 - Check if financial data is available
// Check if financial data is available import FinanceKit let available = FinanceStore.isDataAvailable( .financialData ) guard available else { // No meaningful action can be performed return }
-
8:08 - Present the transaction picker
// Present the transaction picker 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") } } }
-
12:16 - Requesting authorization for financial data
// Requesting authorization for financial data import FinanceKit let store = FinanceStore.shared guard store.isDataAvailable(for: .financialData) else { // No meaningful action can be performed return } let authStatus = await store.requestAuthorization() guard authStatus == .authorized else { // User did not grant access to financial data, stop here return }
-
15:24 - Simple query to retrieve all Apple accounts
// Simple query to retrieve all Apple accounts 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)
-
18:12 - Get latest 7 available balances for account
// Get latest 7 available balances for account 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() }
-
20:27 - Retrieve all the transaction history for an account
// Retrieve all the transaction history for an account 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) }
-
21:04 - Use the history token to resume queries
// Use the history token to resume queries 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) }
-
21:41 - Non monitoring resumable queries
import FinanceKit let store = FinanceStore.shared let account: Account = ... let currentToken = loadToken() let transactionSequence = store.transactionHistory( forAccountID: account.id, since: currentToken, isMonitoring: false ) for try await change in transactionSequence { processChanges(change.inserted, change.updated, change.deleted) persist(token: change.newToken) }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。