大多数浏览器和
Developer App 均支持流媒体播放。
-
StoreKit 2 的新增功能和 Xcode 中的 StoreKit 测试
了解 Xcode 中 StoreKit 2 和 StoreKit 测试的最新增强功能。发现用于推广 App 内购买项目的 API 更新、StoreKit 消息、Transaction 模型、RenewalInfo 模型以及用于管理订阅的 App Store sheet 的 API 更新。了解如何升级到 SHA-256 以进行设备上收据验证以及如何使用 API 创建 SwiftUI 视图。此外,我们还将帮助你开始在 Xcode 中进行 StoreKit 测试,以便你可以调试和测试你的 App 内购买项目和订阅。认识事务检查器,探索 StoreKit 配置编辑器的最新更新,并了解如何模拟 StoreKit 错误来测试你的 App 的错误处理能力。
资源
- Message
- Setting up StoreKit Testing in Xcode
- StoreKit
- Submit feedback
- Supporting promoted In-App Purchases in your app
- Testing failing subscription renewals and In-App Purchases
- Turn on Family Sharing for in-app purchases in App Store Connect
相关视频
WWDC23
WWDC22
WWDC21
-
下载
♪ ♪
Ricky:大家好 我叫 Ricky 欢迎收看 “Xcode 中的 StoreKit 2 和 StoreKit 测试的新功能” 在本次讲座中 我将介绍今年 为 StoreKit 带来的新功能 以及 Xcode 中测试体验的改进 在 2021 年的 WWDC 上 首次介绍了 StoreKit 2 并且引入了使用 Swift 新的更灵活的 API 今年 我很高兴向大家展示 让 StoreKit 2 更为出色的新功能 首先 我将回顾新的 StoreKit 框架功能 然后 我将分享一些使用 StoreKit 构建 SwiftUI App 的更新内容 最后 我将介绍 Xcode 中 StoreKit 测试的新功能 我很高兴介绍一种 全新的 Swift API 它自 iOS 16.4 启用 用于 管理推广的 App 内购买项目 推广的 App 内购买项目是一种 允许你在 App Store 产品页面上展示你的产品的功能 每个产品都有一个专用的推广图片 推广你的 App 内购买项目是 增加产品可见性的一种很好的方式 你可以在 App Store Connect 中轻松设置推广购买 想进一步了解有关配置推广的 App 内购买项目的更多信息 请查看 “App Store Connect 的新功能” 由于推广的 App 内购买项目 在 App Store 上列出 当客户购买推广产品时 他们的交互从点击购买按钮开始 然后 App Store 将购买数据发送到你的 App 你可以监听此信息 并提示他们完成购买 要监听推广购买 请使用 Swift async sequence 每当客户在 App Store 为推广产品发起购买时 该序列都会接收到 一个新的购买意图 首先 创建一个监听器 使用 PurchaseIntent.intents 来接收购买意图 每次序列接收到一个新的对象时 它都包含与购买相关的 StoreKit 产品 你可以像往常一样 通过调用 purchase() 来提示你的客户购买这些产品 这将显示熟悉的付款表单 让他们在那里完成交互 如果你的 App 还没有准备好完成购买 你也可以将购买意图 保存在本地并推迟 稍后可以随时处理
推广 App 内购买项目的 另一个功能是能够自定义 当前设备上的产品显示方式 例如假设客户购买了其中一种产品 你可以将其隐藏 这样当客户浏览或搜索时 App Store 就不再显示 你还能根据 App 的当前状态 更改产品顺序或显示的产品子集 例如游戏中的等级提升 为了涵盖所有这些情况 我很高兴向你展示一些 新的 Swift API 你可以使用这些 API 来自定义 推广的 App 内购买项目的 顺序和可见性 让我们来看一个快速演示 导入 StoreKit 后 你可以使用 Product.PromotionInfo.currentOrder 检查当前的推广顺序 它返回一个按照当前设置顺序排列的 PromotionInfo 对象的序列 如果此列表为空 意味着没有为该设备设置本地覆盖 App Store 中显示的产品顺序 与你在 App Store Connect 中 配置的顺序相同 这个序列中的每个对象 都包含了关于 App 中推广的产品的信息 要设置自定义推广顺序 你可以使用 Product.PromotionInfo.updateProductOrder API 并按照在此设备上的 App Store 中 你希望它们出现的顺序 传递一个产品标识符列表 你还可以隐藏或显示产品 且无需设置全新的顺序 通过更改与 App 内购买项目 相关的可见性属性就可以做到 可见性状态可以是 可见的、隐藏的或默认的 它遵循你在 App Store Connect 中配置的设置 并适用于所有没有 被你的 App 设置本地覆盖的设备 让我们看看 如何在代码中更改这些设置
可见性值可以 通过几种不同的方式进行更改 你的 App 可以调用 Product.PromotionInfo.updateProductVisibility API 并通过传递新的可见性状态和 该 App 内购买项目的 标识符来更改单个产品的值 或者 你还可以通过设置每个 PromotionInfo 对象上的 成员属性来更改可见性值 然后要保存更改 请在刚刚修改过的 对象上调用 update() 这就是你开始使用 StoreKit 2 进行推广的 App 内购买项目 所需的全部内容 现在 我想向你展示 StoreKit 2 中 对核心数据模型的一些改进
这些模型对于 管理 App 内购买项目和 相关信息非常有用 例如购买日期 和订阅状态 我们深入了解下我们正添加到 Transaction 和 RenewalInfo 数据模型中的新字段 这些字段 带来了许多用户所请求的重要改进
第一个新字段是 storefront 它与 storefrontCountryCode 一起出现在 Transaction 模型中 下个新字段称为 reason 它告诉你是客户发起的购买 还是自动订阅续订的交易 在 Product.SubscriptionInfo 下的 RenewalInfo 模型中 有一个名为 nextRenewalDate 的新成员 它告诉你何时处理此订阅续订 所有这些新字段都适用于使用 Xcode 15 构建的 App 尽管最初 是与 iOS 17 一起发布的 但它们中的大多数 也适用于之前的 iOS 版本 前提是你使用的是 StoreKit 2 接下来 我想谈谈 StoreKit Messages 以及我们刚刚添加的出色新功能 我们在去年的 WWDC 2022 引入了 Message API 这是 App Store 向你的客户 发送重要信息的一种方式 Messages 具有一个称为 reason 的值 告诉你消息的目的 你的 App 可以选择 延迟或抑制消息 例如 如果显示消息 会打断客户在交互过程中的操作 你可能会希望延迟消息 除此之外 你的 App 启动时 StoreKit 会自动显示所有活动消息 今年 我们添加了 一个名为 billingIssue 的新消息原因 此消息自 iOS 16.4 启用 当订阅由于计费问题无法续订时 App Store 会发送此消息 StoreKit 会显示 一个计费问题对话框 客户可以在不离开 你的 App 的情况下解决问题 这个新属性已在沙盒中启用 以便给你时间进行测试 请稍后在 Apple Developer 网站上 查看有关 何时为所有客户启用此功能的更新 当 App Store 无法更新订阅时 订阅进入计费重试状态 有关如何在沙盒中 测试此功能的详细信息 请观看有关 测试 App 内购买项目的 WWDC 讲座 现在 让我们回顾 一些与安全相关的更新 为了使 StoreKit 与现代安全实践保持同步 我们正在将我们的收据签名证书 从 SHA-1 迁移到 SHA-256 用于原始 StoreKit 收据 现代的加密库 如 OpenSSL 已经支持 SHA-256 如果你的 App 在设备上执行收据验证操作 那么你需要确保 它能正确处理新的证书 新的 SHA-256 签名证书 从 6 月 20 日开始 将用于在沙盒中签署收据 你将能够在运行 iOS 16.6 及 macOS 13.5 或更高版本的设备上进行测试 8 月 14 日后 新的证书将用于为所有 提交到 App Store 的 新的 App 和 App 更新 签署收据 欲了解更多此时间表的详细信息 可查看 Apple Developer 网站 若你的 App 使用 StoreKit 2 你无需做任何操作 StoreKit 2 的签名交易、 续订信息和 App 交易 已在今天使用 SHA-256 如果你使用 App Store 服务器 API 进行收据验证 你也无需进行任何更改 因为我们会自动处理新的格式 接下来 我很高兴向你展示 一整套 StoreKit 的全新功能 该功能通过使用 SwiftUI 让营销 App 内购买项目 变得非常简单快捷 有些新的 API 可以为单个 App 内购买项目、整个产品商店 甚至包括一些针对订阅的特定 自定义商品创建 SwiftUI 视图 这些视图类似于普通的 SwiftUI 视图 实现起来所需的代码很少 这让你能尽快启动和运行 App 让我们来看看这些新视图 通过这个产品视图 你可以展示 一个单独的 App 内购买项目 包括本地化的标题、描述文件 甚至是可选的促销图片 你可以通过将产品标识符传递给 ProductView 来创建它 无需加载 StoreKit 产品 你只需使用产品 ID 字符串即可 StoreKit 和 SwiftUI 会处理剩下的事情 还有一个新的视图 可以显示整个产品专题 就像一个商店在你的 App 中 与传递一个单个的产品标识符不同 你可以使用产品 ID 专题与 StoreView 结合使用 创建一个商品列表 然后使用 SwiftUI 组件 进一步自定义 StoreView 是一个很好的 方式来启动你的 App 或游戏 并支持 App 内购买项目 因为它只需要几行代码来设置 就像这里所示的一样 最后同样重要的 还有一个新视图 可以展示订阅 及其所有可用的服务级别 使用 SubscriptionStoreView 快速创建自定义页面用于订阅组 就像这个 你只需要订阅组 ID 通常可以在 App Store Connect 中找到 但现在也可以在 Xcode 的 StoreKit 配置中找到 可以轻松将许多可能的自定义 添加到这些新视图类型中 例如 看看如何通过改变几行代码来 创建自定义背景 如此就可以显著改变 SubscriptionStoreView 的外观 随着这些新订阅变化 我们还添加了 一个额外的管理订阅的弹窗 改善用户在 StoreKit App 中的 订阅选择和互动方式 我们来看看它
在这里 你可以看到管理订阅的熟悉流程 向用户展示一个 带有当前活跃层级的弹窗 并提供轻点每个层级的选项 以查看该订阅组中 其他可用的选项列表 为了加快这个过程 并提供更多的定制功能 现在有另一种视图能让你跳过步骤 直接进入你想展示的订阅群组 你可以在你的 App 环境中 选择相关的订阅群组 例如 若你有多个订阅群组 并展示用户可选择的其他服务级别 这个表格可以 使用与常规表格相同的 API: .manageSubscriptionsSheet 同时 需要在传递参数时 额外添加 subscriptionGroupID 参数 关于创建 SwiftUI App 的 StoreKit 还有更多要展示给你的内容 包括其他视图和自定义选项 你可以加以调整让其 适应你的 App 的美学风格 如果你想进一步了解更多信息 Greg 将在讲座 “认识 SwiftUI 的 StoreKit”中详细讲解 在了解 StoreKit 中 App 内购买项目的新功能之后 让我们看看如何使用 Xcode 中的新工具 StoreKit 测试来检验它们 从 Xcode 15 开始 使用 Xcode 中的 StoreKit 测试 你能确保 App 使用 StoreKit 时 无论是在你开始构建它之前 还是之后都提供出色的体验 甚至是你在 App Store Connect 设置任何内容之前 这使得你可以在 Mac 上 进行测试、管理、编辑甚至 创建与 StoreKit 相关的操作 同时在模拟器和设备上都支持 首先是交易管理器 它提供了用于 调试和测试 App 的新功能 现在 它会组织 所有你的测试 App 在导航器中 你可以通过使用 StoreKit 配置进行测试 查看所有当前连接的设备和模拟器 这些设备和模拟器上 安装了你当前的 StoreKit App 这样 你就可以 更轻松、更快速地在多个设备上 同时进行测试 而且不需要 运行你的 App 来查看购买历史记录 让我们来深入了解它的工作原理 我将一台 iPhone 连接到我的 Mac 同时让模拟器保持运行 我正在使用新的开发中的游戏 Backyard Birds 当你打开 Xcode 无论你是否打开了一个项目 你都可以转到调试、 StoreKit、管理交易 然后会出现交易管理器视图 你已经熟悉了 在导航器中 你可以看到所有当前 连接到你的 Mac 上的设备 以我操作的情况为例 这是 实体 iPhone 和模拟器 注意 它们都在这个列表中 显示了几个 App 包括 Backyard Birds 这些都是在 Xcode 中使用 StoreKit 测试的 App 你可以在调试会话之外检查它们 在任一设备上点击一个 App 你可以看到该 App 的 StoreKit 交易列表 并且通过去年介绍的 StoreKit 调试日志 你可以选择查看 特定交易的详细信息 这也适用于我打开 Backyard Birds 的 Xcode 项目 并在模拟器上运行它
在这种情况下 我还可以看到 哪个 App 正在进行调试 因为 App 的名称 旁边会有这个小指示器 交易管理器的更新不止如此 我很兴奋向你展示 你现在可以通过 使用 Xcode 从 Mac 直接为你的 App 进行 App 内购买项目 此外 在创建新的购买项目时 你还可以自定义各种购买参数 例如可消耗产品的数量 或者你可以为订阅选择优惠码
让我们看看如何在实践中运作 以使用 Backyard Birds 为例 首先 你要选择要测试的 App 然后 点击筛选栏左侧的加号按钮 创建一个新的购买项目 弹出窗口中列出了所有适用于 此 App 的可用的 App 内购买项目 这些是我们在 StoreKit 配置中 配置的相同产品 首先 我要购买一个可消耗品 因此我可以选择 “营养饲料”并点击“下一步”
现在 我有机会 配置这个新的购买项目 但如果我想跳过 使用默认选项也是有效的 我对这些设置感到满意 因此让我们点击完成 以使用这些参数完成购买
虽然 App 没有在模拟器运行 但 StoreKit 已接收 并处理了新的购买项目 如果我查看交易列表 顶部现在有一个新项目 点击它 我可以查看 此新交易的详细信息 然而 订阅有所不同 让我们进行另一个购买项目 我将再次点击加号按钮 创建一个新的购买项目 然后 这次我将搜索 此 App 中的一个订阅名称 然后选择我要找的那个 并点击下一步
购买配置选项已经发生了变化 因为这是一种 不同类型的 App 内购买项目 有些选项与以前相同 如购买日期 但也有一些新的选项 我们来看看它们是如何使用的 顺带一提 默认属性在这里也适用
如果在 StoreKit 配置中 进行了设置的话 我可以为此订阅选择一个优惠码 顾客需要输入优惠码和促销码 但为了方便测试 我们提供了可用优惠的列表 供 productID 选择 在这种情况下 我想要修改购买日期 将其设置为一年前的日期 就像是去年的 WWDC 期间 我首次订阅了此服务 接下来 我可以选择 订阅是否该自动续订 或者只保持订阅一个订阅周期 对于这个产品来说 一个订阅周期是一个月 我想要保持自动续订功能开启 这样我可以测试在我开始使用前 App 如何处理主动订阅 让我们点击完成购买 然后通过筛选交易列表来查看结果
正如预期的那样 自去年以来每个月都有一次续订 这些当前都标记为未完成 因为 App 没有运行 并且没有机会接收它们 所以我将在模拟器中运行它 并查看我现在是否可以访问 Backyard Birds Pass
交易管理器中的新功能 适用于支持 Xcode 中的 StoreKit 测试的所有设备和平台 从 iOS 17 和 macOS 14 开始 它们也支持其他平台 包括 iPadOS、watchOS 和 Apple tvOS 如果你正运行较旧版本的操作系统 这对你来说没有任何变化 当你在 Xcode 中 进行激活调试会话时 交易管理器仍兼容以前的所有功能 接下来 我想介绍 StoreKit 配置中的一个新功能 它允许更深入地测试 你的 StoreKit App 在不同情况下的行为 为了演示这一点 我将打开 Backyard Birds 的 Xcode 项目 并转到 StoreKit 配置
现在 在配置的 App 内购买项目上方 有一个名为 Configuration Settings 的新项目 这是我们添加的一个新菜单 用于为你的 App 的测试环境 提供可配置的选项 其中包括一些你可能熟悉的选项 以及从 Xcode 15 开始的 一些新选项 第一和第二部分涵盖所有现有选项 例如用于设置价格与销售范围的 App 使用的默认商店和默认区域 当使用活动的调试会话时 交易管理器 仍与之前的所有相同功能兼容 在 Xcode 的较早版本中 你可以在编辑菜单中找到这些选项 当你打开 StoreKit 配置时 它们仍然存在 并且可以互换使用 当我们谈论 StoreKit 测试选项时 在 iOS 16.4 中 我们新增了新的订阅续订率 这些续订率被设计用于在每次续订时 更改订阅的到期时间 帮助你更快地生成一致的续订 而不需要等待实际订阅期限 这对于快速而可靠地测试 订阅的长期状态非常有用 你可以在编辑菜单和 Xcode 15 的 StoreKit 配置 设置中找到这些新的可配置续订率 StoreKit 配置设置中的 第三部分涵盖了 你可以在 App 中模拟 StoreKit 错误的选项 这是许多人强烈要求的功能 它扩展了以前在编辑菜单中的 Fail Transactions 子菜单 并支持更多的 StoreKit API 和错误类型 每个选项代表着你的 App 可能正在使用的 StoreKit 2 API 它允许你选择 API 应该抛出的错误 每次你的 App 调用 该 API 时都会出现这个错误 此功能支持许多 API 你可以通过它 测试产品加载问题、购买失败、 收据和交易验证问题、 退款请求等等 现在让我们看看 在实际操作中是怎样的
我在左侧打开了 Backyard Birds 的 Xcode 项目 右侧运行着模拟器中的 App 在 StoreKit 配置中 我可以使用复选框启用购买错误 它就在购买 API 名称旁 然后选择应该抛出的错误
我想测试当用户更改商店时 App 如何处理购买 因此我将选择产品不可用
该文件在编辑时会自动保存 并在 App 运行时同步到你的设备上 因此你可以立即测试更改 无需重新运行 App 让我们尝试 购买一个产品 看看会发生什么
果不其然它失败了 它告诉我无法购买此产品 并建议我 联系开发人员获取更多信息
现在让我们禁用购买错误 再次尝试 确保这次可以成功完成
通过这些步骤 我已经有效地测试了 这个 App 的代码可以处理 在进行购买时可能发生的各种错误 这种方法可以应用于 StoreKit 配置设置中的 任何 API 和错误选项 以覆盖多种情况 还能确保你的 App 稳健可靠 这些错误都是 StoreKit 的一部分 你可能已经遇到过它们 每一个都代表了你的 App 中的 一个明确的失败案例 这就是 Xcode 中 StoreKit 测试的所有新功能 今天我展示了如何在调试会话之外的 多个设备上检查和管理交易 如何使用事务管理器购买产品 以测试你的 App 处理现有购买的能力 以及如何模拟 StoreKit 错误 以覆盖多种可能的失败场景
所有这些 Xcode 中的新功能 也可以在代码中 使用 StoreKit Test 框架 为你的 App 编写单元测试 这样你可以编写自动化脚本 来执行所有相同的任务 让我们来看看创建离线购买 并在 XCTest 会话中 设置模拟错误的新 API 要创建新的 App 内购买项目 StoreKitTest 中有个新的 Swift API 它的使用方式与 StoreKit 的 purchase(_:) API 相同 并且具有相同的购买选项 为了支持新的购买功能 如更改购买日期 我们还为测试 添加了仅限购买的新选项 让我们看一个示例 设置 SKTestSession 后 我正在进行离线购买 购买的是一个订阅 并将购买日期设置为一年前的今天 这与我在事务管理器中 所做的购买完全相同 操作也完全相同 使你能够创建可重复的结果 并自动化测试你的 App 这里是关于 StoreKitTest 中的一些新的 API 用于模拟 SKTestSession 中的 StoreKit 错误 它们的工作方式与模拟错误的 StoreKit 配置设置相同 创建测试会话后 你可以调用 setSimulatedError 方法 并传入你想要测试的错误类型 以及应该模拟的 StoreKit API 在此示例中 我选择模拟 loadProducts API 的网络错误 在这个测试中 每次调用 loadProducts API 时 都会抛出相同的错误 要禁用模拟的故障 使用相同的 setter API 并将错误类型替换为 nil 此外 还有用于 新的订阅续订率的 API 可以通过 SKTestSession 中的 timeRate 成员 以相同的方式访问它们 在这个示例测试中 为了进行订阅购买 我添加了一行额外的代码 来设置更快的续订率 并快速生成多个续订 今天介绍了很多 Xcode 中新的 StoreKit 和 StoreKit 测试功能 在 StoreKit 中 有新的 API 来支持推广 App 内购买项目 Transaction 和 RenewalInfo 的 数据模型得到一些强化 以便为你提供更有价值的信息 还有一种新的消息类型 你可以侦听以处理计费问题 SwiftUI 中的新的 StoreKit 视图是一个很好的工具 它可以快速构建 支持产品推广的 App 并且无需额外的代码 即可在所有设备上运行 最后 你可以使用 Xcode 中 提供的 StoreKit 测试工具 验证你 App 中的所有 StoreKit 功能 StoreKit 2 具有许多出色的功能 让你能够构建 令人赞叹的 App 并发展业务 我们添加了许多 充分利用 Swift 的工具 并且通过简单强大的全新 API 让你有机会更加专注于 为客户打造的体验 任何想要推广产品和订阅的 App 现在都可以使用 StoreKit 2 来构建 如果你尚未使用 StoreKit 2 不妨试试看 如果已经使用 请告诉我们 你对我今天展示的新功能的看法 在现有 App 中使用新的 App 内购买项目推广视图 以进一步定制体验 或者完全使用这些 新的 API 来创建一个全新的 App 而且 通过 Xcode 中的 StoreKit 测试 你可以对代码进行测试 并确保一切正常并符合预期 以便在各种条件下 为客户提供最佳体验 有关其他 StoreKit 功能的更多信息 请查看以下链接的讲座 我们期待着看到你使用 StoreKit 2 创造作品 感谢观看
-
-
1:42 - Create a listener for promoted in-app purchases
// Create a listener for promoted in-app purchases import StoreKit let promotedPurchasesListener = Task { for await promotion in PurchaseIntent.intents { // Process promotion let product = promotion.product // Purchase promoted product do { let result = try await product.purchase() // Process result } catch { // Handle error } } }
-
2:57 - Check promotion order
// Check promotion order import StoreKit do { let promotions = try await Product.PromotionInfo.currentOrder if promotions.isEmpty { // No local promotion order set } for promotion in promotions { let productID = promotion.productID let productVisibility = promotion.visibility // Check promoted products } } catch { // Handle error }
-
3:26 - Set a promotion order
// Set a promotion order import StoreKit let newPromotionOrder: [String] = [ "acorns.individual", "nectar.cup", "sunflowerseeds.pile" ] do { try await Product.PromotionInfo.updateProductOrder(byID: newPromotionOrder) } catch { // Handle error }
-
4:02 - Update promotion visibility
// Update promotion visibility import StoreKit // Hide “acorns.individual” do { try await Product.PromotionInfo.updateProductVisibility(.hidden, for: "acorns.individual") } catch { // Handle error }
-
4:17 - Update promotion visibility (alternative method)
// Update promotion visibility import StoreKit do { let promotions = try await Product.PromotionInfo.currentOrder // Hide the first product if var firstPromotion = promotions.first { firstPromotion.visibility = .hidden try await firstPromotion.update() } } catch { // Handle error }
-
8:32 - Product view
// Product view import SwiftUI import StoreKit struct BirdFoodShop: View { let productID: String let productImage: String var body: some View { ProductView(id: productID) { BirdFoodProductIcon(for: productID) } .productViewStyle(.large) } }
-
8:52 - Store view
// Store view import SwiftUI import StoreKit struct BirdFoodShop: View { let productIDs: [String] var body: some View { StoreView(ids: productIDs) { product in BirdFoodIcon(productID: product.id) } } }
-
9:19 - Subscription view
// Subscription view import SwiftUI import StoreKit struct BackyardBirdsPassShop: View { let groupID: String var body: some View { SubscriptionStoreView(groupID: groupID) } }
-
21:09 - Simulated off-device purchase using StoreKitTest
// Simulated off-device purchase using StoreKitTest import StoreKit import StoreKitTest func testSubscriptionRenewal() async throws { let session = try SKTestSession(configurationFileNamed: "Store") let oneYearInterval: TimeInterval = (365 * 24 * 60 * 60) let transaction = try await session.buyProduct( identifier: "birdpass.individual", options: [ .purchaseDate(Date.now - oneYearInterval) ] ) // Inspect transaction }
-
21:48 - Set a simulated purchase error when loading products
// Set a simulated purchase error when loading products import StoreKit import StoreKitTest func testLoadProducts() async throws { let session = try SKTestSession(configurationFileNamed: "Store") let productIDs = [ "acorns.individual", "nectar.cup" ] // Set a simulated error, then load products, expecting an error session.setSimulatedError(.generic(.networkError), forAPI: .loadProducts) do { _ = try await Product.products(for: productIDs) XCTFail("Expected a network error") } catch StoreKitError.networkError(_) { // Expected error thrown, continue... } // Disable simulated error session.setSimulatedError(nil, forAPI: .loadProducts) }
-
22:24 - Set a faster subscription renewal rate in a test session
// Set a faster subscription renewal rate in a test session import StoreKit import StoreKitTest func testSubscriptionRenewal() async throws { let session = try SKTestSession(configurationFileNamed: "Store") // Set renewals to expire every minute session.timeRate = .oneRenewalEveryMinute let transaction = try await session.buyProduct(identifier: "birdpass.individual") // Wait for renewals and inspect transactions }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。