iPhone - SKProductsRequest and "message sent to deallocated instance" - iphone

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!

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];

You probably forgot to nil your request delegate in AppUpgradeViewController's dealloc:
- (void)dealloc {
...
productsRequest.delegate = nil;
[productsRequest release], productsRequest = nil;
...
[super dealloc];
}

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.

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.

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
}

Related

(iphone) having NSThread* as member variable is a bad idea?

One of my custom class has NSThread* as member variable.
I'm letting the thread exit by setting a variable(isNeedToExit).
Then call a release on the thread object [myThread release];
I intended the thread to exit first and release call gets called.
But on second thought, release call might get called before the thread notices the boolean value changed and exit.
(Because the thread is not constantly looking at the value to see if it needs to exit)
#property (nonatomic, retain) NSThread* myThread;
- (void) dealloc
{
...
[myThread release];
myThread = nil;
...
[super dealloc];
}
- (id) initMyClass
{
if(self = [super init] )
{
...
NSThread* aThread = [[NSThread alloc] initWithTarget: self selector:#selector(theThreadMain) object: nil];
self.myThread = aThread;
[aThread release];
[self.myThread start];
..
}
return self;
}
- (void) theThreadMain
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// Add your sources or timers to the run loop and do any other setup.
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
do
{
// Start the run loop but return after each source is handled.
SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);
}
while (self.isNeedToExit == false);
[pool release];
SYSLOG(LOG_DEBUG, "thread exiting");
}
I thought of moving [myThread release] call (now at the class's dealloc) to last line of theThreadMain..
Not sure if this is the correct way to stop a thread when it is a member variable of another class.
Thank you
Actually, your initial assumption was more correct. Your current implementation, however, will not work because dealloc will never be called.
Your class is retained by NSThread in initWithTarget:selector:object and will not be released until the thread finishes executing. Therefore, your class instance will not be deallocated as long as the thread is executing.
One way to solve the problem is to add a "cancel" method to your class that stops the thread. E.g.:
- (void)cancel
{
isNeedToExit = YES;
[myThread release];
myThread = nil;
}
That will cause the thread to stop and will allow your class to be deallocated (once the thread stops.) You need to ensure that all users of your class know that "cancel" needs to be called before the class is released. E.g.:
[myObject cancel]; // Stop the thread
[myObject release];
myObject = nil;
A more traditional solution would be to simply subclass NSThread.

problem with delegate of object which is delegate of NSURLConnection

I've a class PictureDownloader for the purpose of asynchronously loading images from a server. It assigns itself as a delegate of NSURLConnection and as such, is retained by NSURLConnection. I create several of those PictureDownloader in a DetailViewController to fetch the corresponding images, so the DetailViewController is a delegate of each PictureDownloader.
When the user leaves the DetailViewController, all remaining downloads are cancelled, however sometimes it seems to be the case, that a PictureDownloader has finished loading an image (connectionDidFinishedLoading called) before the connection was cancelled, but the DetailViewController doesn't exist anymore (but the PictureDownloader does, because it's retained by NSURLConnection), so the call
[self.delegate didLoadPictureWithID:self.ID];
inside PictureDownloader will give an EXC_BAD_ACCESS or sometimes a "unrecognized selector sent to instance".
Here are the relevant parts of the source code:
creation of the PictureDownloader inside the DetailViewController
- (void)startPictureDownload:(Picture *)pic withPictureId:(NSString *)pId forID:(int)ID
{
PictureDownloader *downloader = [self.downloadsInProgress objectForKey:[NSNumber numberWithInt:ID]];
if(!downloader)
{
downloader = [[PictureDownloader alloc] init];
downloader.picture = pic;
downloader.pictureId = pId;
downloader.ID = ID;
downloader.delegate = self;
[self.downloadsInProgress setObject:downloader forKey:[NSNumber numberWithInt:ID]];
[downloader startDownload];
[downloader release];
}
}
canceling the downloads (called when the DetailViewController returns to the overview)
- (void)cancelAllDownloads
{
[self.downloadsInProgress enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
[obj cancelDownload];
}];
}
delegate method which is called when PictureDownloader finished loading
- (void)didLoadPictureWithID:(int)dID;
{
PictureDownloader *downloader = [self.downloadsInProgress objectForKey:[NSNumber numberWithInt:dID]];
if(downloader)
{
UIImageView *imageView = (UIImageView *)[self.view viewWithTag:dID];
imageView.image = [UIImage imageWithData:downloader.imageData];
[self.downloadsInProgress removeObjectForKey:[NSNumber numberWithInt:dID]];
}
}
cancelDownload method inside PictureDownloader
- (void)cancelDownload
{
[self.imageConnection cancel];
self.imageConnection = nil;
self.imageData = nil;
}
connectionDidFinishedLoading inside PictureDownloader
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if(self.picture)
{
self.picture.data = self.imageData;
NSError *error = nil;
[self.picture.managedObjectContext save:&error];
}
if(self.delegate != nil && [self.delegate respondsToSelector:#selector(didLoadPictureWithID:)] ) //place of failure
[self.delegate didLoadPictureWithID:self.ID];
self.imageData = nil;
self.imageConnection = nil;
}
Can someone give me a hint, how I can deal with this problem?
Help is much appreciated.
To avoid situations like this, I usually add a check like this at the top of connectionDidFinishLoading: and other NSURLConnection delegate methods:
if (connection != self.imageConnection) return;
As another option, you could set the delegate on each PictureDownloader to nil as you cancel it in cancelAllDownloads. Or you could set self.delegate = nil in cancelDownload.
You should check for the existence of the delegate object (and ideally the method/selector) before you attempt to make the call.
For example:
if(self.delegate && [[self.delegate] respondsToSelector:#selector(didLoadPictureWithID:)]) {
...
}
By doing this, you'll ensure that your not attempting to call a delegate that's no longer there. For more information on the respondsToSelector method, see the NSObject Protocol Reference.
When your DetailViewController goes out of scope - dealloc -, set the PictureDownloader's delegate property to nil as well.
Your issue is interesting in that the NSUrlConnection's delegate can't be set to nil in the same way. e.g. When you're PictureDownloader is de-alloced. All you can do is cancel the NSUrlConnection.
The NSURLConnection docs say the following:
Unless a NSURLConnection receives a cancel message, the delegate will receive one and only one of connectionDidFinishLoading:, or connection:didFailWithError: message, but never both. In addition, once either of messages are sent, the delegate will receive no further messages for the given NSURLConnection.
Indicating that the you can verify that the delegate will not be called back after the above messages are received.

Help Troubleshooting NSZombie Error Message

I have a modal view controller which is crashing when it dismisses itself. The error is EXC_BAD_ACCESS (yipee). I am tryin got use NSZombie to work out the problem. I get am getting the following :
2010-10-20 17:15:58.936 [24058:207] AddRunningClient starting device on non-zero client count
2010-10-20 17:16:06.846 [24058:207] * -[ViewController retain]: message sent to deallocated instance 0x6c2d4a0
What does this mean - does it mean that a message was sent to the Viewcontroller or that a message was sent to an object in the Viewcontroller ?
I am really stuck as the thread seems to be main :(
Thanks all in advance for any help,
Martin
EDIT
Thanks all for the quick replies. Here is how I am presenting the view controller :
-(IBAction)letsstartGame {
ViewController * sl = [[ViewController alloc] initWithNibName:#"ViewController" bundle:[NSBundle mainBundle]];
self.viewLink = sl;
[sl release];
[mainMenu stop];
[mainMenu setCurrentTime:0.0];
[self presentModalViewController:viewLink animated:NO];
[viewLink release];
self.viewLink = nil;
}
And dismiss like this :
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (waitingOver) {
[backgroundMain stop];
[fireworks stop];
[self dismissModalViewControllerAnimated:NO];
}
}
That means that you had an instance of an object of type ViewController, it was deallocated, and then you tried to retain it.
edit
You're over-releasing the object. Here's what you're doing:
ViewController * sl = [[ViewController alloc] initWithNibName:#"ViewController" bundle:[NSBundle mainBundle]]; //allocated, has a +1 retain count
self.viewLink = sl; //assuming a retain property, has a +2 retain count
[sl release]; //releasing, now has +1 retain count
....
[viewLink release]; //releasing, now has a 0 retain count
self.viewLink = nil; //attempting to release stale pointer, will result in a crash (perhaps not immediately, but eventually)
Get rid of the [viewLink release] line. It is wrong to have that in there.
It means you are sending a message to a deallocated instance.
So somewhere in your code you have failed to retain an object (probably ViewController) or have released it prematurely.
If you can post your code where you create the View Controller that might be helpful for us to debug.
The message is basically saying that you are trying to send a message (call a function) on/to an object that has already been dealloc'd (released and the memory freed). If you could send more code, I could perhaps attempt to determine why.

The dealloc method is not called in the present modal view contrller

It is in My view controller
-(void)doctorsListAction
{
if(isFirst == YES)
{
[self getDoctorsListController];
[[self navigationController] presentModalViewController:doctorListViewNavigationController animated:YES];
[doctorListViewController release];
}
}
-(void)getDoctorsListController
{
//DoctorListViewController *doctorListViewController=[[[DoctorListViewController alloc]initWithNibName:nil bundle:nil]autorelease];
doctorListViewController=[[DoctorListViewController alloc]init];
doctorListViewNavigationController=[[UINavigationController alloc]initWithRootViewController:doctorListViewController];
doctorListViewController.doctorList=doctorList;
doctorListViewNavigationController.navigationBar.barStyle= UIBarStyleBlackOpaque;
[doctorListViewController release];
}
It is in DoctorListViewContrller
-(void)closeAction
{
printf("\n hai i am in close action*******************************");
//[doctorList release];
//[myTableView release];
//myTableView=nil;
printf("\n myTableView retainCount :%d",[myTableView retainCount]);
[[self navigationController] dismissModalViewControllerAnimated:YES];
}
//this method is not called I don't know why if it not called i will get memory issues
- (void)dealloc
{
printf("\n hai i am in dealloc of Doctor list view contrller");
[doctorList release];
[myTableView release];
myTableView=nil;
[super dealloc];
}
this method is not called I don't know
why if it not called i will get memory
issues
When exactly dealloc gets called (i.e. when the object is deallocated) shouldn't really matter to you. What matters is that you pair up each alloc with a release/autorelease. Which you are likely not doing.
The above code doesn't read very well and looks a bit "Java"-ish. Your "get" method doesn't actually return anything, which looks strange. But you normally wouldn't name a method "get___" anyway.
You're probably leaking memory in your getDoctorsListController method on this line:
doctorListViewNavigationController=[[UINavigationController alloc]initWithRootViewController:doctorListViewController];
Since you didn't define doctorListViewNavigationController in this method, and I assume you posted code that compiles, it is either a member (although not necessarily a property) of your class or a static variable somewhere. Which means it could already be pointing to an object. Which means when you assign a new alloc'ed object to it, the old one is lost (leaked).
Here's how you should refactor it.
- (void)doctorsListAction
{
if (isFirst == YES)
{
[self showDoctorsList];
}
}
- (void)showDoctorsList
{
DoctorListViewController* doctorListViewController = [[DoctorListViewController alloc] initWithNibName:nil bundle:nil];
doctorListViewController.doctorList = doctorList;
UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:doctorListViewController];
navController.navigationBar.barStyle = UIBarStyleBlackOpaque;
[self.navigationController presentModalViewController:navController animated:YES];
[navController release];
[doctorListViewController release];
}
There might be a lot of other objects 'behind the scenes' that want to keep the DoctorListViewController around. If you just balance out your retains and releases, you should be ok.
Also in -(void)doctorsListAction, shouldn't [doctorListViewController release]; be [doctorListViewNavigationController release]; instead?

Do I need to set these variables to nil in viewDidLoad as I have in this code?

- (void)viewDidUnload {
self.GPSArray = nil;
self.accelerometerArray = nil;
self.headingArray = nil;
self.managedObjectContext = nil;
self.locationManager = nil;
self.pointLabel = nil;
self.accelerometerLabel= nil;
self.headingLabel= nil;
self.startStop = nil;
self.lastAccelerometerReading = nil;
self.lastGPSReading = nil;
self.lastHeadingReading = nil;
}
- (void)dealloc {
[GPSArray release];
[accelerometerArray release];
[headingArray release];
[managedObjectContext release];
[locationManager release];
[pointLabel release];
[accelerometerLabel release];
[headingLabel release];
[startStop release];
[lastAccelerometerReading release];
[lastGPSReading release];
[lastHeadingReading release];
[super dealloc];
}
The reason viewDidUnload is called, is that your view is being released and any view resources should be freed.
So, you only need to free view related items.
In your case it looks like you'd only need to free the UILabels that are probably in your view. If they were marked as IBOutlets and not in assign properties, you'd want to release the memory used by them:
self.pointLabel = nil;
self.accelerometerLabel= nil;
self.headingLabel= nil;
That also means, that in viewDidLoad if you are setting up the other properties you want to make sure they are not being allocated again if they are there already as it can be called again if the view is unloaded and then reloaded again.
The reason this would be called is if the view controller received a memory warning. You can test this memory warning in the simulator to see how viewDidUnload and viewDidLoad are called.
You don't have to, and if you run this, you'll be wasting some substantial execution time. Using the property method to set a property to nil is the same thing as releasing the property, with the caveat that some extra stuff may or may not happen, depending on how you've set up the setter methods.
So let's walk through this code. At the end of your viewDidUnload method, all of your properties are now nil. The object is then deallocated, and your object attempts to release a dozen nil objects or so. Now, the Objective-C runtime is pretty smart, and if you send a message to nil, (surprise surprise) nothing will happen.
So you've basically got a dozen lines that do absolutely nothing.
no, you shouldn't.
if you do as if the above code, you are wasting effort.
By setting them to nil in viewDidUnload, they will be going thru a process of release and retain automatically, which means, by the time the code get to dealloc, they are actually released and nil, and you are doing another release there for the nil.
Releasing nil object might be erratic.
So, ignore those in viewDidUnload.