In App Purchase/View Controller Crash: Message sent to deallocated instance - iphone

I have a button on my view controller that presents my In App Purchases store.
storeSinglePlayer *ssp = [[storeSinglePlayer alloc] initWithNibName:#"storeSinglePlayer" bundle:nil];
//Animation Code
CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromTop;
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[self.navigationController pushViewController:ssp animated:NO];
The navigation controller successfully pushes this store on the screen. The store has a back button which executes the following code:
[self.request cancel];
self.request.delegate = nil;
self.request = nil;
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
CATransition* transition = [CATransition animation];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromBottom;
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[self.navigationController popViewControllerAnimated:NO];
This executes successfully as well. But if I now click the button to show the store view controller again, I get the message:
-[storeSinglePlayer respondsToSelector:]: message sent to deallocated instance 0xd642df0
This is a very famous problem indeed. And as you'll notice in the code above, I have incorporated the suggestions I came across various posts on stackoverflow. The following code has been implemented:
[self.request cancel];
self.request.delegate = nil;
self.request = nil;
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
Also I have my property and corresponding synthesize set as:
#property (nonatomic, strong) SKProductsRequest *request;
#synthesize request = _request;
I have no clue, why is it crashing!
P.S: The project is ARC enabled.

Something is calling your ssp after it has been released by ARC. The code you posted doesn't make it clear where that is happening.
One thing that most likely would solve the problem would be to keep a reference with a member variable+property to your ssp in your class declaration, instead of createing a new one every time you click the button. Just init it once when your first viewcontroller loads and reuse it instead.
If you want to investigate further, you could try to comment out the blocks setting up the transition and see if it still crashes. Maybe the navigationcontroller makes some call after the ssp has been released.

When a message is sent to a deallocated instance, an object was released from the memory and you are attempting to use it again. ARC must be releasing the object since it is automatically detecting that you no longer need the it. Try adding one of the following to your header file.
#property (nonatomic, strong) storeSinglePlayer *ssp;
or
storeSinglePlayer *__strong ssp;
The important word above is strong. It tells ARC that you want to retain this object for later use.

I got where I was messing up. I was handling multiple rotations using the following code:
[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:#"storeSinglePlayerLandscape"] owner:self options:nil];
[self viewDidLoad];
I realize now, this not the best practice, as it calls viewDidLoad multiple times. Still, that's not really the cause of the problem. It works fine, when the store is displayed only a few times and returned back to the previous view controller. But say after 10-15 times, there are too many requests which would return with the product and hence send the error , "message sent to deallocated instance".
I commented out the code, and it works fine now.
I know, this is a very typical problem, which most users might not face. But just in case, you used implemented some bad code like me, may be this information works!

Related

Having issue updating hud ivar during in-app purchase

I have recently set-up an in-app purchase mechanism in my app. During the purchase i would like to update an hud (i am using the mbprogresshud) according to two kinds of events: i start a purchase and i receive a validation of the purchase. The problem i am facing is that the hud is never updated with the one i want when the purchase is done (custom view):
When i click on the purchase button:
-(IBAction)buyButtonTapped:(id)sender {
self.hud = [[SCLProgressHUD alloc] initWithView:self.view];
[self.view addSubview:self.hud];
self.hud.labelText = #"Connecting...";
self.hud.minSize = CGSizeMake(100 , 100);
[self.hud show:YES];
...
}
When i receive a notification that the purchase was successful:
-(void)productPurchased:(NSNotification *)notification {
self.hud.customView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"checkmark_icon.png"]];
self.hud.mode = SCLProgressHUDModeCustomView;
self.hud.labelText = #"Thanks for your purchase!";
...
}
Setting the self.hud.customView property in the last method will trigger a [self setNeedsLayout]; and [self setNeedsDisplay] within the hud class but still i don't observe any change.
Any possible idea of what i am doing wrong here?
As mentioned on the mbprogress hud readme, update of the UI when tasks are performed on the main thread required a slight delay to take effect. What happen in my case is that the hud was a strong property of a popover controller that i dismiss immediately so i didn't get a chance to see the update happening. I now dismiss the controller in the completion block of:
-(void)showAnimated:(BOOL)animated
whileExecutingBlock:(dispatch_block_t)block completionBlock:(void
(^)())completion
And my code snippet look like like this for the dismiss:
[_hud showAnimated:YES whileExecutingBlock:^(void){
[self.successPurchaseSoundEffect play];
_hud.customView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"checkmark_icon.png"]];
_hud.mode = SCLProgressHUDModeCustomView;
_hud.labelText = #"Thanks!";
// We need to pause the background thread to let the music play and the hud be updated
sleep(1);}
completionBlock:^(void){
[self.delegate dismissPurchaseInfoController:self];
}];

Why is my AdMob code leaking in my iphone app?

I have included AdMob in my iphone app. I have the following code in my viewDidLoad method:
bannerView_ = [[GADBannerView alloc]
initWithFrame:CGRectMake(0.0,
0.0,
320,
50)];
// Specify the ad's "unit identifier." This is your AdMob Publisher ID.
bannerView_.adUnitID = ADMOB_BANNER_UNIT_ID;
// Let the runtime know which UIViewController to restore after taking
// the user wherever the ad goes and add it to the view hierarchy.
bannerView_.rootViewController = self;
[self.view addSubview:bannerView_];
// Initiate a generic request to load it with an ad.
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
[bannerView_ loadRequest:r];
[r release];
This leaks. When I comment out the second last line ( [bannerView_ loadRequest:r]; ), the leak disappears. The only thing I changed in this code from the example provided by Google was to introduce the variable r so I could put AdMob in testing mode. In the code supplied by Google, bannerView_ is released by viewDidUnload. I looked for the loadRequest method but all I found was a definition in the GADBannerView.h file. As far as I can tell, there is no GADBannerView.m file, which seems weird in itself. Anyway, any tips would be much appreciated.
Thanks,
John
Why don't you use this AdMediator - it is very simple to configure and you don't have to worry about AdMob and iAds (if you want to use it) :
will swap in AdMob ads if no iAds are
available.
Are you releasing bannerView_ in your dealloc method or somewhere else appropriate ?
Instead of:
GADRequest *r = [[GADRequest alloc] init];
You could try:
GADRequest *r = [GADRequest request];

How to identify CAAnimation in delegate and release controller?

I have seen How to identify CAAnimation within the animationDidStop delegate?, this is an addition to it.
I'm unable to get this working properly. I have an animation, and I'd like to release the controller that it was run in after the end of the animation.
Example: The controller translates from right -> left then releases itself.
Defining the animation:
NSValue *end = [NSValue valueWithCGPoint:CGPointMake(800, self.view.center.y)];
NSValue *start = [NSValue valueWithCGPoint:self.view.center];
CABasicAnimation *moveAnimation;
moveAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
moveAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
moveAnimation.duration = 0.45f;
moveAnimation.fromValue = start;
moveAnimation.toValue = end;
// actually set the position
[self.view.layer setPosition:[end CGPointValue]];
moveAnimation.delegate = self;
moveAnimation.removedOnCompletion = NO;
[self.view.layer addAnimation:moveAnimation forKey:MOVING_OUT];
Inside the delegate method:
- (void) animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
CAAnimation *check = [self.view.layer animationForKey:MOVING_OUT];
if (theAnimation == check)
{
//[check release];
[self release];
}
}
If I leave this code as-is, my controller doesn't get dealloc'd (due to the retain call by the animation).
If I run [check release], I get the message sent to deallocated instance.
Does anyone know what's wrong?
Is there another way to identify a CAAnimation in the animationDidStop delegate WITHOUT specifying removedOnCompletion = NO?
EDIT:
Forgot to mention. By not specifying that removedOnCompletion = NO, animationForKey: will return NULL. Hence I'm unable to identify the animation.
Thanks!
I think the eventual reason is CAAnimation.delegate is a retain property (very strange oops!).
The header file definition is:
/* The delegate of the animation. This object is retained for the
* lifetime of the animation object. Defaults to nil. See below for the
* supported delegate methods. */
#property(retain) id delegate;
To let self get release, the animation must be removed from layer, like:
[self.view.layer removeAnimationForKey:#THE_ANIMATION_KEY];
It's not clear what your problem is here, but it may help you to know that CAAnimation instances are generic KVO containers, so you can add custom info to them:
[myAnimation setValue: #"check" forKey: #"name"];
You can then check against that:
if ([[theAnimation valueForKey: #"name"] isEqual: #"check"])
// ...
Does that help?

Why does popViewController only work every other time

I am totally stumped, here's the situation:
My app uses the Core Location framework to get the current location of the user and then pings my server at TrailBehind for interesting places nearby and displays them as a list. No problems.
To conserve batteries, I turn off the GPS service after I get my data from the server. If the user moves around while using the app and wants a new list he clicks "Refresh" on the navigation controller and the CLLocation service is again activated, a new batch of data is retrieved from the server and the table is redrawn.
While the app is grabbing data from my server I load a loading screen with a spinning globe that says "Loading, please wait" and I hide the navigation bar so they don't hit "back".
So, the initial data grab from the server goes flawlessly.
The FIRST time I hit refresh all the code executes to get a new location, ping the server again for a new list of data and updates the cells. However, instead of loading the table view as it should it restores the navigation controller bar for the table view but still shows my loading view in the main window. This is only true on the device, everything works totally fine in the simulator.
The SECOND time I hit refresh the function works normally.
The THIRD time I hit refresh it fails as above.
The FOURTH time I hit refresh it works normally.
The FIFTH time I hit refresh it fails as above.
etc etc, even refreshes succeed and odd refreshes fail. I stepped over all my code line by line and everything seems to be executing normally. I actually continued stepping over the core instructions and after a huge amount of clicking "step over" I found that the table view DOES actually display on the screen at some point in CFRunLoopRunSpecific, but I then clicked "continue" and my loading view took over the screen.
I am absolutely baffled. Please help!! Many thanks in advance for your insight.
Video of the strange behavior:
Relevant Code:
RootViewControllerMethods (This is the base view for this TableView project)
- (void)viewDidLoad {
//Start the Current Location controller as soon as the program starts. The Controller calls delegate methods
//that will update the list and refresh
[MyCLController sharedInstance].delegate = self;
[[MyCLController sharedInstance].locationManager startUpdatingLocation];
lv = [[LoadingViewController alloc] initWithNibName:#"Loading" bundle:nil];
[self.navigationController pushViewController:lv animated:YES];
[super viewDidLoad];
}
- (void)updateClicked {
//When the location is successfully updated the UpdateCells method will stop the CL manager from updating, so when we want to update the location
//all we have to do is start it up again. I hope.
[[MyCLController sharedInstance].locationManager startUpdatingLocation];
[self.navigationController pushViewController:lv animated:YES];
//LV is a class object which is of type UIViewController and contains my spinning globe/loading view.
}
-(void)updateCells {
//When the Core Location controller has updated its location it calls this metod. The method sends a request for a JSON dictionary
//to trailbehind and stores the response in the class variable jsonArray. reloadData is then called which causes the table to
//re-initialize the table with the new data in jsonArray and display it on the screen.
[[MyCLController sharedInstance].locationManager stopUpdatingLocation];
if(self.navigationController.visibleViewController != self) {
self.urlString = [NSString stringWithFormat:#"http://www.trailbehind.com/iphone/nodes/%#/%#/2/10",self.lat,self.lon];
NSURL *jsonURL = [NSURL URLWithString:self.urlString];
NSString *jsonData = [[NSString alloc] initWithContentsOfURL:jsonURL];
NSLog(#"JsonData = %# \n", jsonURL);
self.jsonArray = [jsonData JSONValue];
[self.tableView reloadData];
[self.navigationController popToRootViewControllerAnimated:YES];
[jsonData release];
}
}
CLController Methods: Basically just sends all the data straight back to the RootViewController
// Called when the location is updated
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
NSLog(#"New Location: %# \n", newLocation);
NSLog(#"Old Location: %# \n", oldLocation);
#synchronized(self) {
NSNumber *lat = [[[NSNumber alloc] init] autorelease];
NSNumber *lon = [[[NSNumber alloc] init] autorelease];
lat = [NSNumber numberWithFloat:newLocation.coordinate.latitude];
lon = [NSNumber numberWithFloat:newLocation.coordinate.longitude];
[self.delegate noteLat:lat];
[self.delegate noteLon:lon];
[self.delegate noteNewLocation:newLocation];
[self.delegate updateCells];
}
}
The first thought is that you may not want to send startUpdatingLocation to the CLLocationManager until after you've pushed your loading view. Often the first -locationManager:didUpdateToLocation:fromLocation: message will appear instantly with cached GPS data. This only matters if you're acting on every message and not filtering the GPS data as shown in your sample code here. However, this would not cause the situation you've described - it would cause the loading screen to get stuck.
I've experienced similarly weird behavior like this in a different situation where I was trying to pop to the root view controller when switching to a different tab and the call wasn't being made in the correct place. I believe the popToRootViewController was being called twice for me. My suspicion is that your loading view is either being pushed twice or popped twice.
I recommend implementing -viewWillAppear:, -viewDidAppear:, -viewWillDisappear: and -viewDidDisappear: with minimal logging in your LoadingViewController.
- (void)viewWillAppear:(BOOL)animated {
NSLog(#"[%# viewWillAppear:%d]", [self class], animated);
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
NSLog(#"[%# viewDidAppear:%d]", [self class], animated);
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
NSLog(#"[%# viewWillDisappear:%d]", [self class], animated);
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated {
NSLog(#"[%# viewDidDisappear:%d]", [self class], animated);
[super viewDidDisappear:animated];
}
Then, run a test on your device to see if they are always being sent to your view controller and how often. You might add some logging to -updateClicked to reveal double-taps.
Another thought, while your #synchronized block is a good idea, it will only hold off other threads from executing those statements until the first thread exits the block. I suggest moving the -stopUpdatingLocation message to be the first statement inside that #synchronized block. That way, once you decide to act on some new GPS data you immediately tell CLLocationManager to stop sending new data.
Can you try and debug your application to see where the control goes when calling updateCells? Doesn't seem to be anything apparently wrong with the app.
Make sure that there are no memory warnings while you are in the LoadingViewController class. If there is a memory warning and your RootViewController's view is being released, then the viewDidLoad will be called again when you do a pop to RootViewController.
Keep breakpoints in viewDidLoad and updateCells. Are you sure you are not calling LoadingViewController anywhere else?
So, I never did get this to work. I observe this behavior on the device only every time I call popViewController programatically instead of allowing the default back button on the navigation controller to do the popping.
My workaround was to build a custom loading view, and flip the screen to that view every time there would be a delay due to accessing the internet. My method takes a boolean variable of yes or no - yes switches to the loading screen and no switches back to the normal view. Here's the code:
- (void)switchViewsToLoading:(BOOL)loading {
// Start the Animation Block
CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromLeft forView:self.tableView cache:YES];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:.75];
// Animations
if(loading) {
if (lv == nil) { lv = [[LoadingViewController alloc] initWithNibName:#"Loading" bundle:nil]; }
[self.view addSubview:lv.view];
[self.view sendSubviewToBack:self.tableView];
self.title = #"TrailBehind";
}
else {
[lv.view removeFromSuperview];
}
// Commit Animation Block
[UIView commitAnimations];
//It looks kind of dumb to animate the nav bar buttons, so set those here
if(loading) {
self.navigationItem.rightBarButtonItem = nil;
self.navigationItem.leftBarButtonItem = nil;
self.title = #"TrailBehind";
}
else {
UIBarButtonItem *feedback = [[UIBarButtonItem alloc] initWithTitle:#"Feedback" style:UIBarButtonItemStylePlain target:self action:#selector(feedbackClicked)];
self.navigationItem.rightBarButtonItem = feedback;
UIBarButtonItem *update = [[UIBarButtonItem alloc] initWithTitle:#"Move Me" style:UIBarButtonItemStylePlain target:self action:#selector(updateClicked)];
self.navigationItem.leftBarButtonItem = update;
[feedback release];
[update release];
}
}
Looking at your original code, I suspect this block very much:
- (void)viewDidLoad {
...
lv = [[LoadingViewController alloc] initWithNibName:#"Loading" bundle:nil];
[self.navigationController pushViewController:lv animated:YES];
[super viewDidLoad];
}
viewDidLoad is called every time the NIB is loaded, which can happen multiple times, especially if you run low on memory (something that seems likely given your remark that it only happens on device). I recommend that you implement -didReciveMemoryWarning, and after calling super, at the very least print a log so you can see whether it's happening to you.
The thing that bothers me about the code above is that you're almost certainly leaking lv, meaning that there may be an increasing number of LoadingViewControllers running around. You say it's a class variable. Do you really mean it's an instance variable? ivars should always use accessors (self.lv or [self lv] rather than lv). Do not directly assign to them; you will almost always do it wrong (as you are likely dong here).
I came across this while searching for the exact same issue, so while I'm sure you've already solved your problem by now, I figured I'd post my solution in case someone else runs across it...
This error seems to be caused when you assign two IBActions to the same UIButton in interface builder. It turned out that the button I used to push the view controller onto the stack was assigned to two IBActions, and each one was pushing a different controller onto the navigationController's stack (although you'll only end up seeing one of them - perhaps the last one to be called). So anyway, pressing the back button on the topmost view doesn't really dismiss it (or maybe it's dismissing the 2nd, unseen controller), and you have to press twice to get back.
Anyway, check your buttons and be sure they're only assigned to a single IBAction. That fixed it for me.

How to save password in iphone?

I need to create an IPhone Application...Which will communicate with a website...so it requires username and password...Now I need to know how to save the username and password in iphone...I have already used NSuserdefaults...but that didnt solved my pblm....Also I need to restore the view...from where the user exits the application...So I request you to help me?
Regards and thanks for the support,
Syam
I am a bit paranoid about saving secure data (username/passwords) on plain text files such as plist files or NSUserDefaults.
Apple provides Keychain services to store secure data. It is slightly complicated, view the documentation Keychain Services Tasks for iPhone OS
They also provide Generic Keychain app to explore.
You can store the password on a regular file, or you can also store it on a SQLite database.
I had a logout functionality in the app I did recently.
I had a hierarchy as :
"BaseUITableViewController
^
|
"Any subclass of UITAbleView Controller used in project."
all of these subclasses had a "Logout" button and the method to call logout service was written in Base class.
as soon as the response for logout request is valid and the session is killed I call following method (which of your interest in contrast to the above text :))
-(void)gotoMainScreenOnLogout
{
self.navigationController.navigationBarHidden = YES;//to make the previous view invisible
self.navigationController.toolbarHidden=YES; //27 JUNE UPDATE
[self.view removeFromSuperview]; //27 JUNE UPDATE
// MYAPPViewController *homeViewController = [[MYAPPViewController alloc]initWithNibName:#"MYAPPViewController" bundle:nil];
MYAPP_LoginUIVIewController *homeViewController =[[MYAPP_LoginUIVIewController alloc] initWithNibName:#"MYAPP_LoginUIVIewController" bundle:nil];
UINavigationController *nc = [[UINavigationController alloc]initWithRootViewController:homeViewController ];
[nc.navigationBar setBarStyle:UIBarStyleBlackOpaque];
//
UIWindow *MYAPP_window = [[[UIApplication sharedApplication] windows]objectAtIndex:0];
CATransition *transition = [CATransition animation];
transition.duration = 0.8;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionMoveIn;
transition.subtype = kCATransitionFade;
transition.delegate = self;
//...
[MYAPP_window.layer addAnimation:transition forKey:nil];
[MYAPP_window removeAllSubviews]; // not needed anymore
[MYAPP_window addSubview:nc.view];
[homeViewController release];
}
hope this helps.