一个StoreKit引发的异步访问Crash记录

一. 现象

我们某个App出现上述crash报告,目前只发现一例,内存访问异常引起

闪退位置:

img

闪退位置代码是在给一个数组进行复制,value是请求到的SKProduct对象。

二. Crash分析

XYStoreProductService类用于请求商品生成SKProduct对象,具体过程是使用SKProductsRequest,根据identifier发起一个请求,在回调里面获取SKProduct

1
2
3
4
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{

}

但是这个回调是异步的,在子线程中返回,返回时间依赖于当前手机的网络环境,可能会比较慢。

在回调中会往XYStore中的数组products添加SKProduct对象

1
2
3
4
- (void)addProduct:(SKProduct*)product
{
self.products[product.productIdentifier] = product;
}

添加动作是在子线程的,所以就可能出现同步问题,具体例子如下:

用户进入购买页会请求所有商品项,还没有返回成功的情况下,用户点击购买按钮,因为此时商品还没有返回,所以又会出发请求商品项的动作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SKProduct *product = [self productForIdentifier:identifier];
if (product) {
success(product);
return;
}

// 若内存中没有,网络获取
NSSet *set = [[NSSet alloc] initWithArray:@[identifier]];

[self requestProducts:set success:^(NSArray *products, NSArray *invalidProductIdentifiers)
{
if (products.count > 0) {
if (success) {
success(products.firstObject);
}
}
} failure:failure];

此时会同时出现两次商品请求,两次返回,两个线程,同时对products进行设置操作,就有几率导致内存访问异常而crash。

这个crash的概率极低,需要满足以下条件:

  1. 第一次的request返回很慢
  2. 用户购买的商品的时候,商品项不存在
  3. 两次请求几乎同时返回

三. 如何解决

对products数组的访问添加同步方法即可,示例代码如下

1
2
3
4
5
6
7
8
9
10
11
12
- (SKProduct*)productForIdentifier:(NSString*)productIdentifier
{
@synchronized (self) {
return self.products[productIdentifier];
}
}
- (void)addProduct:(SKProduct*)product
{
@synchronized (self) {
self.products[product.productIdentifier] = product;
}
}