That's all I can get from the crash when I do in the applicationDidBecomeActive
MyStoreObserver * observer = [[MyStoreObserver alloc] initWithContext:self.managedObjectContext andDelegate:self];
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];
But the weird thing is I have been able to purchase twice in the past and even if I delete the app re-reun everything I cannot escape this pit. It doesn't seems logic. I can alloc init the observer alone without problem and I can call the default paymentQueue without problem but if I try to add the transaction observer to the queue, life stop. It's on ARC and the delegate I added to the observer is not the problem. I know for sure.
I've added an nslog to every method in the observer and none get called before the crash
SOLUTION FOUND
Okay, well it looks like the observer is not retained by the queue and thus the observer needs to be an instance variable.
Thanks
Okay, well it looks like the observer is not retained by the queue and thus the observer needs to be an instance variable (or retained in some way).
To add an official source to this answer:
Inside the header file SKPaymentQueue.h of the Framework Storekit is the following clear comment:
// Observers are not retained.
// The transactions array will only be synchronized with the server while the queue has observers.
// This may require that the user authenticate.
- (void)addTransactionObserver:(id <SKPaymentTransactionObserver>)observer __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);
Related
I have created a game with a in game shop view and view controller.
The shop can be accessed in then menu (ViewController.m) and from the Game Over screen (GameViewController.m).
My problem is that if I have displayed the shop once in the menu, and then play a game and access the shop in the game over screen and try to buy something, the app crashes trowing a EXC_BAD_ACCESS error without much info. (Breaking at
[[SKPaymentQueue defaultQueue] addPayment:lPayment];
line in the ButtonPressed action in my ShopViewController when trying to buy a IAP.
My view are set up like this:
Menuview -> Ladderview -> Gameview -> ShopView
and
Menuview -> Shopview
Hope you can help me pinpoint the error,
EDIT -----------
It seems that I can reproduce the error from the menu -> Shopview without using the game view. I can do this by pressing a "buy button", pressing cancel, navigate back to the menu, go back to the shop, and repeat. On the 3-4th attempt it crashes at the same line. Here is the entire button pressed method:
- (void)buyButtonPressed:(UIButton *)pButton {
NSInteger lTag = [pButton tag];
//////NSLog(#"Button tag: %i"), lTag;
Reachability *lReachability = [Reachability reachabilityForInternetConnection];
NetworkStatus lCurrentNetworkStatus = [lReachability currentReachabilityStatus];
if (lCurrentNetworkStatus != NotReachable) {
if ([SKPaymentQueue canMakePayments]) {
SKPayment *lPayment = [SKPayment paymentWithProduct:[mPriceArray objectAtIndex:lTag]];
[[SKPaymentQueue defaultQueue] addPayment:lPayment];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
} else {
[self showAlertViewWithText:#"Purchases are disabled. Please check your settings for General -> Restrictions -> In-App Purchases and try again." andTitle:#"Warning"];
}
} else {
[self showAlertViewWithText:#"No network connection!" andTitle:#"Warning"];
}
}
So it might seem as the lPayment is being deallocated. I even tried to set
mProductIds = nil;
mPriceArray = nil;
when I remove the shop view, trying to force it to allocate it again when I reload the shop, but without any luck.
Thanks
Your issue is a dangling pointer. EXC_BAD_ACCESS is the CPU moaning that you are addressing non-existent memory or memory which is outside of your access rights area. The cause is a lack of retainment of an object which causes early deallocation and then is overwritten. At which time (which may be delayed), the pointer will point to garbage whose dereference (class examination) causes an EXC_BAD_ACCESS to be thrown. This error canNot be caught using #try. There is an assumption here that the stack itself is corrupt causing continuation to be impossible (although such is most likely not the case), which will throw the debugger for a spin, whose current state output is already lacking in many areas. It is like uncontrollable anarchy when the CPU resets important registers and performs a long jump.
consider Automatic Reference Counting. In you are already there, consider that delegate-like properties are not retained by the host object. Any property which could logically contain self will not retain any value stored in it. ARC will not help you there.
in your case: defaultQueue is probably good. lPayment has probably been deallocated.
Try to trace the problem at first enabling NSZombie . In case of EXC_BAD_Access Problem some time it(NSZombie ) becomes more useful to trace deallocated object than simple guessing where the problem is.
It is hard to tell from the information provided, but it could be the following: Your statement
SKPayment *lPayment = [SKPayment paymentWithProduct:[mPriceArray objectAtIndex:lTag]];
instantiates an SKPayment object, and hands it over to the current autorelease pool. If this pool does not exist (this might be the case if the code runs in a separate thread for which no autorelease pool has been set up explicitly), the object is released immediately again, and your statement
[[SKPaymentQueue defaultQueue] addPayment:lPayment];
accesses invalid memory.
I'm uisng addObserver:selector:name:object: in viewDidLoad.
And I'm using removeObserver:name:object: in viewWillDisappear:animated: to remove observer.
What will happen if I failed to remove observer by passing wrong parameter to removeObserver:name:object:?
(For example, observer isn't removed if I pass wrong notification to parameter name or wrong object to object or Observer)
If the observer still not nil after calling removeObserver:name:object:, I can find out that removing observer failed because notificationSelector will being called.
But if the observer become nil after calling removeObserver:name:object:, I can not find out whether removing observer failed or not.
Will observers automatically removed when observer become nil?
Or does notification dispatch table of NSNotificationCenter became larger and larger and eventually the app become slow?
EDIT
When I use subclass of UIViewController object for observer, the app doesn't crash after ViewController's dealloc are called.
But when I use a object of other class, the app crashs after the object's dealloc are called.
Update: From -[NotificationCenter removeObserver:]:
If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method. Otherwise, you should call this method or removeObserver:name:object: before observer or any object specified in addObserverForName:object:queue:usingBlock: or addObserver:selector:name:object: is deallocated.
Old answer:
Observers are not removed automatically. From the NSNotificationCenter Class Reference:
Important: The notification center does not retain its observers,
therefore, you must ensure that you unregister observers (using
removeObserver: or removeObserver:name:object:) before they are
deallocated. (If you don't, you will generate a runtime error if the
center sends a message to a freed object.)
You should therefore call
[[NSNotificationCenter defaultCenter] removeObserver:self];
in your dealloc method if you are not 100% sure that the observer was not removed previously.
You just need to put in the correct Observer for the Observer to be removed. If you pass the wrong parameter to name or object (or nil), the receiver will not use them as criteria for removal.
All Cocoa programs have a default NSNotificationCenter, so once you remove the observers you shouldn't have to worry about the it taking up more memory.
I understand that instance variables are released in dealloc (as shown below), but when exactly is it called? Are all instance variables released upon app close, or is there an accepted way for them to be deallocated individually as they become unneeded?
- (void)dealloc {
[fred release];
[wilma release];
[barney release];
[betty release];
[super dealloc];
}
Like any other object, the app delegate will be deallocated when no other object has retained it. It's pretty unusual to have an app delegate that doesn't stick around until the app terminates, and as others have pointed out, the app may not bother to release and deallocate anything just before it exits.
I think it's a safe bet that the app delegate would be deallocated if no object other than the app had retained it and you gave the application a new delegate. Aside from that unusual situation, the app delegate's -dealloc method probably doesn't get called very often at all. However, that doesn't mean that you shouldn't implement it correctly -- it's expected behavior, and things could easily change in a future iOS release.
I have figured out how all of the StoreKit stuff works and have actually tested working code... however, I have a problem.
I made my "store" layer/scene the SKProductsRequestDelegate. Is this the correct thing to do? I get the initial product info like so:
SKProductsRequest *productRequest = [[SKProductsRequest alloc] initWithProductIdentifiers: productIDs];
[productRequest setDelegate: self];
[productRequest start];
The problem is that if I transition to a new scene when a request is in progress, the current layer is retained by the productRequest. This means that touches on my new scene/layer are handled by both the new layer and the old layer.
I could cancel the productRequest when leaving the scene, but:
I do not know if it is in progress at that point.
I cannot release it because it may or may not have been released by the request delegates.
There has got to be a better way to do this. I could make the delegate a class external to the current layer, but then I do not know how to easily update the layer with the product information when the handler is called.
OK, problem solved.... ergh.
I went ahead and made the store a separate class, and solved the issue of callbacks to the scene by adding a delegate to the class, which holds the layer of the Store interface. When the transactions finish, I can use the delegate to call back to my scene/layer.
I solved the issue of not knowing if the delegate has been released by using the respondsToSelector: method before attempting to send a message to it.
It turns out the real bug was caused by my attempt to fix 1 & 2 in the first place. I overrode onExit to let me know when to remove the class as the store delegate. It turns out I forgot to call [super onExit], which is where the scene is released. Hence, it stayed retained and did not get removed from the touchHandler. Oops!
I want to create an object in Objective C but I don't hold a reference to it.
Is it allowed to let the object control its own lifetime by calling [self release]?
In case you're wondering why I need this: I want to create an object that subscribes to some notifications, but after a while the object is no longer needed and should go away.
So, is the following allowed?
- (void) destroyMyself {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self release];
}
If you see this in code, its probably wrong. However there are legitimate response for it in certain circumstances that are arguably defensible. (So make sure you are doing it for the right reasons.)
A good example of when this makes sense, is when you create an object that goes off to download a url. The object sits in memory while downloading the url, then sends a message to its delegate saying the data is ready (or url couldn't be downloaded). Once its message has been sent it destroys itself as its no longer needed. In this situation the code/function that created the 'url downloader' may no longer even be in memory, i.e. if it was called in response to a user selection a menu item or an action in a view controller that is no longer on the screen.
This is useful when the code that creates the "download" object doesn't care if the download completes or not.
The rules are simple. You should only release an object if you own it. i.e. the object was obtained with a method starting "new" or "alloc" or a method containing copy.
Cocoa Memory Management Rules
An object must not therefore do [self release] or [self autorelease] unless it has previously done [self retain].
To quote the great philosopher Alicia Silverstone, "I had an overwhelming sense of ickiness" when I read that. But I couldn't really tell you why.
I think I would use autorelease rather than a simple release since you're still executing code in self when you call it, but other than that I can't think of any technical reasons why it wouldn't work.
It's legal, but be careful. You want to be sure nothing else is going to send you a message after you release yourself.
I've done this kind of thing for a faulting scheme back before we had CoreData.
Well part of the protocol is that if you send release to self, then you should have sent retain once as well, which I suppose you do. Then there is nothing fishy. I mean the allocing code must be able to control the lifetime of your instance; it itself can only prolong its life, never make it shorter (since making it shorter, then you'd suddenly leave the allocing owner of the instance with an invalid pointer).
And I will use [self autorelease] instead of [self release]. Because usually it's called in
- (void)aMethod
{
[self.delegate aDelegateMethod:self];
[self release];
//If you add code related to self here, after [self release], you are making a huge mistake.
}
If I use [self autorelease], I can still do something after autorelease.