A completion handler that calls EXC_BAD_ACCESS when the same method is called twice

I am working on some IAP using this tutorial .

Firstly, I get the following products:

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

The assistant launches the following:

 - (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler { @synchronized(self) { // 1 _completionHandler = [completionHandler copy]; // 2 _productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers]; _productsRequest.delegate = self; [_productsRequest start]; } } 

When products return or fail, the following is called:

 #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; } 

Question
The problem is that the user runs the selection or products twice. For example, extraction products are called in viewDidLoad, but if the user has a poor / slow connection and the transition is made, then back to the controller. The original selection is not canceled, so two operations are performed.

I believe the problem is when the second is returned and the pointer has changed / does not exist / is damaged.

Error code EXC_BAD_ACCESS 2 in the corresponding line:

 _completionHandler(YES, skProducts); 

OR

 _completionHandler(NO, nil); 
+6
source share
1 answer

You're right. It does not exist when the second response is returned, since it is processed after processing the first response: completionHandler = nil .

In such a situation, I consider it safe to always check if a block exists before it is called:

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

(and the same in -request:didFailWithError: . In your current implementation, calling [[IAPHelper sharedInstance] requestProductsWithCompletionHandler:nil] will cause the same failure without this check (try it!).

In addition to these security checks, it would be better to cancel your first request when necessary, for example, when the user will move and will not see the answer in any case. Also, in -requestProductsWithCompletionHandler: either _productsRequest existing _productsRequest before creating a new one, or checking an existing _productsRequest to decide whether to create a new one or not, would be another useful level of security.

+14
source

Source: https://habr.com/ru/post/974155/


All Articles