4、商品信息获取:使用SKProductsRequest请求商品列表、处理请求回调、解析商品信息
好,到了这一步,我们已经在 App Store Connect 里把商品都配好了。但说实话,那只是「后台工作」。用户打开你的 App,看到的商品列表从哪来?
嗯,得靠代码去拉。
这一节,我们就来搞定商品信息的获取。说白了,就是从苹果的服务器上,把那些商品的标题、价格、描述给拽下来,展示给用户看。
4.1 为什么不能硬编码商品信息?
你可能想:我直接在代码里写死「6元」、「去广告」、「高级会员」不就行了?
千万别。我在第一个内购项目里就这么干过,结果吃了大亏。
原因有三:
- 价格会变:苹果允许你随时调整价格,但你的 App 代码是死的。硬编码意味着用户看到的价格永远是旧版本。
- 本地化问题:不同国家的用户看到的货币符号、价格格式都不一样。你写死「¥6.00」,美国用户看到的就是乱码。
- 商品状态不可控:商品可能被下架、被苹果拒审、或者暂时不可用。硬编码无法感知这些变化。
4.2 SKProductsRequest 的基本用法
苹果提供了一个专门的类来做这件事:SKProductsRequest。它的工作流程很简单:
- 你创建一个请求,传入商品的 Product ID 列表
- 苹果服务器返回一个响应,里面包含商品信息
- 你解析响应,展示给用户
来看代码:
// 1. 准备商品 ID 列表
let productIdentifiers: Set<String> = [
"com.example.remove_ads",
"com.example.premium_monthly",
"com.example.premium_yearly"
]
// 2. 创建请求
let request = SKProductsRequest(productIdentifiers: productIdentifiers)
request.delegate = self
// 3. 发起请求
request.start()
这里有个细节:productIdentifiers 是一个 Set<String>,不是数组。为什么?因为苹果内部用集合来去重,你传重复的 ID 也没意义。
4.3 处理请求回调
请求发出去后,苹果会异步回调。你需要实现 SKProductsRequestDelegate 协议。
我个人习惯把回调处理写在一个单独的扩展里,这样代码更清晰:
extension StoreManager: SKProductsRequestDelegate {
// 请求成功
func productsRequest(_ request: SKProductsRequest,
didReceive response: SKProductsResponse) {
// 有效的商品
let validProducts = response.products
// 无效的商品 ID
let invalidIdentifiers = response.invalidProductIdentifiers
// 处理结果
DispatchQueue.main.async {
self.handleProductsResponse(valid: validProducts,
invalid: invalidIdentifiers)
}
}
// 请求失败
func request(_ request: SKRequest, didFailWithError error: Error) {
print("商品请求失败: \(error.localizedDescription)")
// 这里可以展示错误提示给用户
}
}
你想想看,回调里给了你两个东西:
response.products:苹果确认有效的商品列表response.invalidProductIdentifiers:苹果不认识的 ID 列表
invalidProductIdentifiers 不为空,别急着怀疑代码。先去 App Store Connect 检查一下:商品状态是不是「准备提交」?协议有没有签?银行信息填了没?我曾经花了一下午 debug,最后发现是税务信息没填完。
4.4 解析商品信息
拿到 SKProduct 对象后,怎么提取信息?
每个 SKProduct 包含以下关键属性:
| 属性 | 类型 | 说明 |
|---|---|---|
productIdentifier |
String | 商品唯一 ID,和你配置的一致 |
localizedTitle |
String | 本地化后的商品名称 |
localizedDescription |
String | 本地化后的商品描述 |
price |
NSDecimalNumber | 商品价格(纯数字,不含货币符号) |
priceLocale |
Locale | 价格对应的本地化信息(含货币代码) |
这里有个坑:price 是 NSDecimalNumber,不是 Double。为什么?因为价格计算不能有浮点精度误差。你想想看,如果价格是 0.99 美元,用 Double 可能变成 0.9900000001,这能忍?
正确的价格格式化方式:
func formatPrice(for product: SKProduct) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = product.priceLocale
return formatter.string(from: product.price) ?? "\(product.price)"
}
这样,美国用户看到的是 $0.99,中国用户看到的是 ¥6.00,日本用户看到的是 ¥100。完全自动适配。
4.5 完整的商品获取流程
把上面这些串起来,一个完整的商品获取流程大概是这样的:
class StoreManager: NSObject {
static let shared = StoreManager()
private var products: [SKProduct] = []
func fetchProducts() {
let ids: Set<String> = [
"com.example.remove_ads",
"com.example.premium_monthly"
]
let request = SKProductsRequest(productIdentifiers: ids)
request.delegate = self
request.start()
}
func product(for identifier: String) -> SKProduct? {
return products.first { $0.productIdentifier == identifier }
}
}
extension StoreManager: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest,
didReceive response: SKProductsResponse) {
products = response.products
// 打印调试信息
for product in products {
print("商品: \(product.localizedTitle)")
print("价格: \(formatPrice(for: product))")
print("描述: \(product.localizedDescription)")
}
// 通知 UI 更新
NotificationCenter.default.post(name: .productsLoaded, object: nil)
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("获取商品失败: \(error.localizedDescription)")
}
}
- 商品信息必须从苹果服务器获取,不能硬编码
- 使用
SKProductsRequest发起请求 - 实现
SKProductsRequestDelegate处理回调 - 用
NumberFormatter和priceLocale格式化价格 - 注意处理无效商品 ID 和网络错误
4.6 避坑指南
最后,分享几个我踩过的坑:
- 沙箱环境 vs 生产环境:开发测试时,苹果返回的是沙箱环境的商品信息。价格可能显示为 0.00 美元,别慌,这是正常的。
- 请求时机:不要在 App 启动时立刻请求。我建议等用户进入商店页面时再请求,避免浪费用户流量。
- 缓存策略:商品信息在短时间内不会变。你可以缓存到内存里,下次直接使用,不用重复请求。
- 网络异常:用户可能在弱网环境下打开商店。记得加个重试按钮,或者显示「加载中」的状态。
嗯,商品信息获取这块就这些。说白了就是三步:发请求、收回调、解析展示。但每一步都有细节,尤其是价格格式化那块,做对了用户体验会好很多。
下一节,我们开始处理真正的支付流程。到时候你会看到,商品获取只是开胃菜,支付回调才是重头戏。