4

My app used in app purchases and my references here.

When i am loading products from server by a block, at the same time I switch to other tab inside UITabBarController and app crashed when products loaded

This is my code

//Load products from server
[[LAInAppHelper sharedInstance] requestProductsWithCompletionHandler:^(BOOL success, NSArray *products) {
    if (success) {
        // even i do nothing in here app till crashed
    }
}];

If I delete this lines I can switch between any tab. Nothing throw by app when crashed, even i enable Zombie objects. Just bad access

rmaddy
  • 314,917
  • 42
  • 532
  • 579
crz
  • 438
  • 5
  • 18

1 Answers1

3

There is a problem in the implementation of the LAInAppHelper in the tutorial that you linked: the helper treats your application as non-concurrent.

Here is what is going on: the shared instance of LAInAppHelper has a sharedInstance, which owns _completionHandler (among other things).

The requestProductsWithCompletionHandler: method assigns _completionHandler a copy of the block that has been passed in. This is OK for the first request, but if another request is "in flight", the completion block of that other request will be released by ARC due to this reassignment. If the tab to which you switch starts a concurrent request, the initial request will come back to a released block, causing an undefined behavior, and possibly a crash.

To fix this problem, you need to split the class in two - one part holding items common to all requests (namely, _productIdentifiers and _purchasedProductIdentifiers) and the request-specific ones (_productsRequest and _completionHandler).

The instance of the first class (let's call it LAInAppHelper) remains shared; instances of the second class (let's call it LAInAppHelperRequest) are created per-request inside the requestProductsWithCompletionHandler: method.

-(id)initWithHelper:(LAInAppHelper*)helper
    andCompletionHandler:(RequestProductsCompletionHandler)completionHandler {
    if (self = [super init]) {
        _completionHandler = [completionHandler copy];
        _productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:helper.productIdentifiers]; // You will need to make productIdentifiers a property
        _productsRequest.delegate = self;
        [_productsRequest start];
    }
    return self;
}

You will need to create a block that wraps the _completionHandler, too, like this:

- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler {
    __block LAInAppHelperRequest *req = [[LAInAppHelperRequest alloc] initWithHelper:self andCompletionHandler:^(BOOL success, NSArray *products) {
         completionHandler(success, products);
         req = nil;
    }];
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • so many Thanks @dasblinkenlight, your answer is really useful for my knowledge but it not resolve my problem. And i've found reason why app crashed, b'cos I clicked on another tap on TabBarController, that tab called one more time method [[LAInAppHelper sharedInstance] requestProductsWithCompletionHandler:^(BOOL success, NSArray *products) to get list of bought products. So app crashed! Thank again for your time to answer my question! Any idea to help me call again that method to get list of bought product – crz Jul 26 '13 at 03:32
  • @crz It looks like the crash is precisely the result of the problem I mentioned then - calling on the `sharedInstance` concurrently from another tab. Did you try implementing my fix? How did it go? – Sergey Kalinichenko Jul 26 '13 at 09:58