5

I got troubles implementing InAppPurchase. My implementation of purchase is in modal view controller (AppUpgradeViewController), that I present from another modal view. I do it like this:

AppUpgradeViewController * appUpgradeViewController = [[AppUpgradeViewController alloc] init];
appUpgradeViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
appUpgradeViewController.delegate = self;
[self presentModalViewController:appUpgradeViewController animated:YES];
[appUpgradeViewController release];

Then, in my upgrade view I do the following:

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
NSSet *productIdentifiers = [NSSet setWithObject:kInAppPurchaseProUpgradeProductId];
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
self.productsRequest.delegate = self;
[productsRequest start];

Then I have implemented

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

where I do:

[self.productsRequest release];

and then I have other required methods.

The problem is when I show modal, and quickly dismiss it then after few seconds i got the following on console (I turned on NSZombieEnabled):

*** -[AppUpgradeViewController respondsToSelector:]: message sent to deallocated instance 0x2e91f0

I suppose that it's something with that product request, but I don't know how to debug or fix it. It seems that the answer for request comes to this controller just after it's dismissed (and deallocated), but I don't know how to prevent it from receiving messages after dismiss/dealloc. Thanks for any help!

Marcin
  • 51
  • 1
  • 4
  • I am having the same problem, but none of the below solutions work for me. I have ARC enabled. Any suggestions? – mvb Aug 11 '12 at 07:10

5 Answers5

9

I had the same problem. Not with a modal view, but with a pushed view on the navigation controller stack. When I quickly navigated back before my product information was loaded via SKProductsRequest, it also throws me an message sent to deallocated instance exception. I solved this by calling the cancel method (see reference) on my SKProductsRequest object.

Additional to this I also set the delegate to nil.

This is my product request:

NSSet *productIdentifiers = [NSSet setWithObject:@"MY_IDENTIFIER"];
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
productsRequest.delegate = self;
[productsRequest start];

and this is what I called in the dealloc method to cancel it.

productsRequest.delegate = nil;
[productsRequest cancel];
productsRequest = nil;

I also removed the observer from the SKPaymentQueue like described in this answer for another question.

[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
Community
  • 1
  • 1
disco crazy
  • 31,313
  • 12
  • 80
  • 83
  • This took so long to figure out. Enable Zombie objects caught this which was throwing me off `-[IAPurchaseViewController retain]: message sent to deallocated instance`. Thank you! – alexgophermix Sep 26 '16 at 21:07
  • I don't understand why you (and me) have to `nil` the delegate during cancellation. It has to go right away after all references are gone, but something keeps it alive along with the delegate reference to call back to... – Yevhen Dubinin Oct 05 '20 at 20:01
4

You probably forgot to nil your request delegate in AppUpgradeViewController's dealloc:

- (void)dealloc {
   ...
   productsRequest.delegate = nil;
   [productsRequest release], productsRequest = nil;
   ...
   [super dealloc];
}
Jilouc
  • 12,684
  • 4
  • 46
  • 43
2

I guess that's because you have released your productsRequest, but it seems you haven't set the pointer to nil which means it's still pointing at the now-invalid memory location.

How is the productsRequest property defined ? If it has the retain option, then instead of:

[self.productsRequest release];

you need to do:

self.productsRequest = nil; // Property will do the release for you.

If it has the assign option, then you need to do:

[self.productsRequest release];
self.productsRequest = nil; // Or else some might access this pointer,
                            // which now might point to nirvana.
DarkDust
  • 90,870
  • 19
  • 190
  • 224
  • It's defined like this: @property (nonatomic, retain) SKProductsRequest * productsRequest; – Marcin Jan 24 '11 at 16:07
  • In that case, all you need to do is `self.productsRequest = nil;`. Do **not** do `[self.productsRequest release];`, this will result in memory errors. – DarkDust Jan 24 '11 at 16:21
  • It's defined like this: @property (nonatomic, retain) SKProductsRequest * productsRequest; But I only release it in the "- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response" method, so it's not released until controller receives response. If I dismiss modal immediately, before controller receives response, this will not happen. – Marcin Jan 24 '11 at 16:21
  • But I tried to do "[self.productsRequest release];" in my dealloc method, and error is: "*** -[SKProductsRequest release]: message sent to deallocated instance 0x20c010". I've alos tried this in dealloc: "self.productsRequest.delegate = nil;", but the error is: "*** -[SKProductsRequest setDelegate:]: message sent to deallocated instance 0x2f3090". When I do only "productsRequest = nil;" in dealloc, I have the same error as previous: "*** -[AppUpgradeViewController respondsToSelector:]: message sent to deallocated instance 0x2f9590" – Marcin Jan 24 '11 at 16:22
  • Sorry, my first comment has been cut ;] – Marcin Jan 24 '11 at 16:22
  • I think a likely source for the last error you've cited is `self.productsRequest.delegate = self;`. Delegates are usually not retained, so the request finally receives something and wants to call the delegate but the delegate is gone. In your dealloc method, be sure to run `[self.productsRequest cancel];` before you set it to nil. – DarkDust Jan 24 '11 at 16:27
  • Still no success. I've removed any occurrence of release on productRequest, and I only have in my dealloc: [self.productsRequest cancel]; self.productsRequest = nil;, but the error is the same as above. I think you're right, that sth is calling that controller, when it's already gone, but I can't figure out what it is. – Marcin Jan 24 '11 at 16:35
  • Also have a look at this tutorial: http://troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/ I was doing it like that. Look closer at the way the productRequest is released, I thought I was doing it the right way... – Marcin Jan 24 '11 at 16:37
  • Is there a way to find out *what* (what object, what method) exactly is causing this error: *** -[AppUpgradeViewController respondsToSelector:]: message sent to deallocated instance 0x42871a0 ? – Marcin Jan 24 '11 at 16:40
  • If you run the project in the debugger it should stop where the message is sent to the deallocated object, IIRC. – DarkDust Jan 24 '11 at 16:53
  • 3
    Finally, solved! I think the correct set of instructions in my dealloc method is: self.productsRequest.delegate = nil; [self.productsRequest cancel]; self.productsRequest = nil; [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; – Marcin Jan 24 '11 at 18:01
  • Thanks for your help! Also very helpful was that topic http://stackoverflow.com/questions/4150926/in-app-purchase-crashes-on-skpaymentqueue-defaultqueue-addpaymentpayment – Marcin Jan 24 '11 at 18:03
0

Swift 3

It's a good idea to close the request if you started it. This is a safe way to do it in Swift.

    // strong reference at top of class
    var productRequest: SKProductsRequest!

    // at some point you will fetch products

    // on deallocating the window
    if productRequest != nil {
        productRequest.cancel()
        productRequest.delegate = nil
    }
skymook
  • 3,401
  • 35
  • 39
0

Is it because you're doing this:

[appUpgradeViewController release]; 

too early?

Try doing it in the dealloc method of whatever class you're allocing it in. Providing you're not allocing it more than once, of course. This would also require you to move your declaration into the class header.

Dave
  • 3,438
  • 20
  • 13
  • I don't think I do it too early. It's likely something is calling that controller's method, when it definitely mustn't. – Marcin Jan 24 '11 at 16:26