Я работаю над некоторыми IAP, используя это руководство .

Сначала я получаю продукты с помощью этого:

-(void)fetchAvailableProductsFirstLoad:(BOOL)firstTimeLoading {
    [[IAPHelper sharedInstance] requestProductsWithCompletionHandler:^(BOOL success, NSArray *products) { ...

Помощник выполняет следующее:

- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler {

    @synchronized(self) {
        // 1
        _completionHandler = [completionHandler copy];

        // 2
        _productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers];
        _productsRequest.delegate = self;
        [_productsRequest start];
    }
}

Когда продукты возвращаются или не работают, вызывается следующее:

#pragma mark - SKProductsRequestDelegate

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {

    NSLog(@"Loaded list of products...");
    _productsRequest = nil;

    NSArray * skProducts = response.products;
    for (SKProduct * skProduct in skProducts) {
        NSLog(@"Found product: %@ %@ %0.2f",
              skProduct.productIdentifier,
              skProduct.localizedTitle,
              skProduct.price.floatValue);
    }

    _completionHandler(YES, skProducts);
    _completionHandler = nil;

}

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {

    NSLog(@"Failed to load list of products.");
    NSLog(@"Error: %@",error);
    _productsRequest = nil;

    _completionHandler(NO, nil);
    _completionHandler = nil;

}

Проблема
У нас возникает проблема, когда пользователь дважды запускает выборку или продукты. Например, выборка продуктов вызывается в viewDidLoad, но если у пользователя плохое / медленное соединение, и он уходит, а затем возвращается к контроллеру. Первоначальная выборка не отменяется, поэтому выполняется две операции.

Я считаю, что проблема в том, что возвращается второй и указатель изменился / не существует / поврежден.

Ошибка кода 2 EXC_BAD_ACCESS возникает в соответствующей строке:

_completionHandler(YES, skProducts);

ИЛИ

_completionHandler(NO, nil);
6
StuartM 21 Авг 2014 в 16:37

1 ответ

Лучший ответ

Ты прав. Его не существует, когда возвращается второй ответ, потому что он обнуляется после обработки первого ответа: completionHandler = nil.

В такой ситуации я считаю безопасным всегда проверять, существует ли блок перед его вызовом:

if (_completionHandler) {
    _completionHandler(YES, skProducts);
    _completionHandler = nil;
}

(и то же самое в -request:didFailWithError:). В вашей текущей реализации вызов [[IAPHelper sharedInstance] requestProductsWithCompletionHandler:nil] вызвал бы такой же сбой без этой проверки (попробуйте!).

Помимо этих проверок безопасности, было бы лучше отменить ваш первый запрос, когда это необходимо, например, когда пользователь перемещается и все равно не видит ответа. Кроме того, в -requestProductsWithCompletionHandler:, либо отмена существующего _productsRequest перед созданием нового, либо проверка существующего _productsRequest, чтобы решить, создавать ли новый, будет еще одним полезным слоем безопасность.

17
stefandouganhyde 21 Авг 2014 в 17:21
1
Спасибо, я использовал части обоих ответов, но это было ближе всего. Я также добавил дополнительный метод -(void)cancelProductRequest { [_productsRequest cancel]; _productsRequest = nil; }, который отменяет текущий запрос, если они уходят от этого контроллера, чтобы сохранить несколько запросов.
 – 
StuartM
22 Авг 2014 в 19:47
Отличный @StuartM, определенно лучше отменить запрос, если он вам не нужен. Рад, что смог помочь!
 – 
stefandouganhyde
26 Авг 2014 в 12:43