NSNotificationCenter removeObserver:name:object: not removing observer - objective-c

I have a method in a view controller that sets up some notifications:
- (void)processState
{
MYGame *game = [[MYGameManager sharedInstance] getGameAtIndex:self.indexPath.row];
if(game)
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notification_gameUpdated:) name:kMYNotificationGameUpdated object:game];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notification_gameEnded:) name:kMYNotificationGameEnded object:game];
}
}
Then there's a game updated method, which is called every so often:
- (void)notification_gameUpdated:(NSNotification *)notification
{
MYGame *game = notification.object;
_game_status = (game.entity.bet.isWinning) ? MYGameStatusWin : MYGameStatusLose;
}
And finally, when the game ends:
- (void)notification_gameEnded:(NSNotification *)notification
{
MYGame *game = notification.object;
// Clear the notifications
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMYNotificationGameUpdated object:game];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMYNotificationGameEnded object:game];
self.gameIsActive = NO;
}
Trouble is, that even when I remove the observers (and a breakpoint shows that this is happening), then the notification_gameUpdated: method is still being called. If I change it to
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMYNotificationGameUpdated object:nil];
This still won't clear it. But if I change it to
[[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:game];
Then that does clear it. As does
[[NSNotificationCenter defaultCenter] removeObserver:self];
But I'm not keen on doing either, because I'd rather the code was clean and I don't want any "gotchas" further down the line if I need to add more observers. I've checked the rest of the code and cannot find any other classes adding observers to this object, although other view controllers do listen to the same messages.

Is processState called more than once? That would explain the behavior you are seeing.
If it is, one way to fix the issue would be to always remove listeners before adding them. See e.g. this answer.

edit #2
try registering with object:nil and when you post the notification include the reference to game in the userInfo dictionary. then, in the receiver, you can compare against game and perform whatever action you want if it is a match. this should get you the same behavior as if you were using object:game, although it does not explain why your current implementation isn't working
when you register for notifications like this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(notification_gameUpdated:)
name:kMYNotificationGameUpdated
object:game];
the #selector will only be performed if that particular instance of game is the sender.
is it possible that you're re-initializing your shared instance of game after registering? that could cause the behavior you're experiencing
try registering for notifications with object:nil and see what happens. (assuming there are not multiple games running concurrently)

So it turned out that the reason for the issue was Method Swizzling. The project I'm working on has addObserver:selector:name:object: and removeObserver:name:object: swizzled. The issue was that although addObserver has been handled correctly, removeObserver is only removing objects on specific conditions. This will obviously need to be changed...
But I post this as a warning to others... Swizzling can be dangerous to your health!
Apologies for any time wasted.

Related

Saving some data from applicationDidEnterBackground method

I would like to save some data from an array into a plist file when the applicationDidEnterBackground is called. I'm trying to figure it out how to access my array from the applicationDidEnterBackground method. Is there any best practice to do this?
Many thanks
Marcos
Put the code in the class that actually has the data. Have the class register for the UIApplicationDidEnterBackgroundNotification notification.
// Put this in the `init` method
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(backgrounding) name:UIApplicationDidEnterBackgroundNotification object:nil];
// The method that gets called
- (void)backgrounding {
// save the data
}
// Put this in the `dealloc` method
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
With this setup you don't have to get anything into the UIApplicationDelegate and the responsibility is kept where it belongs.

Removing a NSNotificationCenter observer in iOS 5 ARC

I have an iOS 5 ARC-based project, and am having difficulty about where I should be removing the observer for the NSNotificationCenter observations which I have registered within a UIViewController. Similar posts on SO have said this should be done in the -dealloc method. Even though this method is not required in ARC projects I have added it with the following code:
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
As a test, I open the UIViewController (within a UINavigationController), do some things which trigger the notifications, and then pop it off the stack by tapping the Back button. I then reopen the UIViewController, and do some more things to trigger the notifications, but notice that each callback is being called twice - an indication that the previous notifications have not been deregistered. Repeating this procedure just causes each callback to be called more than more times, so they appear to never be deregistering.
Any help would be appreciated!
It's pretty clear your dealloc method isn't being called (nor is the removeObserver call).
Why not remove your UIViewController's observer in the viewDidUnload: or viewWillDisappear: methods?
If your dealloc isn't being called, it's likely because someone is still holding a reference to the view controller. Perhaps you need to mark something as __weak? You can use the allocations instrument to help track down what's holding on to your view controller.
"I also need the notification callbacks to still be fired if the view is off-screen" -> you may need to register UIApplicationWillEnterForegroundNotification. If so, let try this:
- (void)viewWillAppear:(BOOL)animated {
NSLog(#"viewWillAppear");
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
NSLog(#"viewWillDisappear");
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
NSLog(#"applicationWillEnterForeground");
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
// do your stuff here
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(#"applicationDidEnterBackground");
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
The idea is adding or removing UIApplicationDidEnterBackgroundNotification whenever coming in and out of your screen. We just register UIApplicationWillEnterForegroundNotification when the app enter background and remove once it's back. Be noticed that we just remove UIApplicationDidEnterBackgroundNotification when viewWillDisappear.
My dealloc() is not called by somehow, so I found this way, hope it useful for you too.
Enjoy :)

How to call a selector in a different class?

I'm trying to use this code but Xcode returns an error because the method I'm trying to call in the selector:#selector() is in another class. Thanks for your help!
AppDelegate.m:
-(void)applicationDidBecomeActive:(UIApplication *)application{
[..]
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMethodHere) name:UIApplicationDidBecomeActiveNotification object:nil];
}
MainViewController.m:
-(void)myMethodHere{
[..]
}
The problem is that you use
addObserver:self
which means that it looks for the function in the current class. Instead do something like
addObserver:instanceOfOtherClass
Update
Add the call to the init method of MainViewController
// MainViewController.m
- (id)init;
{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(someMethod) name:UIApplicationDidBecomeActiveNotification object:nil];
}
return self;
}
Make sure to remove yourself in dealloc
- (void)dealloc;
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
By doing it this way from the very moment the object comes in to existence it is ready to receive notifications and then when it is being deallocated it will safely remove itself.
A good pattern to follow is to make the class that is doing the observing responsible for registering for notifications. This keeps encapsulation well and removes some risk of sending notification to deallocated instances.
Rationale
You need to balance your calls for registering for notifications and unregistering for notifications otherwise a message may be called on a deallocated object which could be hard to track down.
If I have a class that needs to be notified of an event the likely hood is I will register for the notifications in the init method and then unregister for the notifications in the dealloc (init and dealloc are just examples of times I often do this, not necessarily the best place in every example, do what makes sense in your case).
The issue is your use of
addObserver:self
The observer needs to be an instance class that contains the method you want to call, so create that first and then add the notification. Something like.
-(void)applicationDidBecomeActive:(UIApplication *)application{
[..]
SomeClass *newObject = [[SomeClass alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:newObject selector:#selector(someMethodContainedInSomeclass) name:UIApplicationDidBecomeActiveNotification object:nil];
}

How to reloadData?

I am not dumb and I know how to reload data. I am in a tricky situation where I have a UIView inside another UIView both named OHGridView. I have to keep them named the same way.
With the OHGridView example code, the refresh looked a little like this:
[(OHGridView *)self.view reloadData];
But now that I added a UIView, it no longer works.
Any help is appreciated!
Edit:
Code removed
The NSNotificationCenter may be what you need. You can register for events (eg a perform update event) and then post these events from anywhere. These go to the notification center and then to your class/view. When the event is received, you just do what is needed.
The docs are here:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/Reference/Reference.html
Inside the OHGridView you would call during initialization:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(ReloadNotification:) name:#"ReloadOHGridView" object:nil];
Then, just define the method:
- (void)ReloadNotification:(NSNotification *)notification
{
[self reloadData];
}
So, when you want an update to occur, you then just call:
[[NSNotificationCenter defaultCenter] postNotificationName:#"ReloadOHGridView" object:self];
When you deallocate the OHGridView you should remove the observer:
[[NSNotificationCenter defaultCenter] removeObserver:self];

Receiving UIPasteboard (generalPasteboard) notification while in the background

In there a way to do this? I register my object for UIPasteboardChangedNotification at launch time, but when sending it to the background and opening (for instance) Safari and copying some text, my handler never gets called.
(I'm using just the simulator for now).
I've used both:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(pasteboardNotificationReceived:)
name:UIPasteboardChangedNotification
object:[UIPasteboard generalPasteboard]];
and:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(pasteboardNotificationReceived:)
name:UIPasteboardChangedNotification
object:nil ];
to register my handler.
I had the same problem. According to the UIPasteboard Class Reference documentation for the changeCount property (emphasis is mine):
Whenever the contents of a pasteboard changes—specifically, when pasteboard items are added, modified, or removed—UIPasteboard increments the value of this property. After it increments the change count, UIPasteboard posts the notifications named UIPasteboardChangedNotification (for additions and modifications) and UIPasteboardRemovedNotification (for removals). ... The class also updates the change count when an application reactivates and another application has changed the pasteboard contents. When users restart a device, the change count is reset to zero.
I had read this to mean that my application would receive UIPasteboardChangedNotification notifications once my app was reactivated. A careful reading reveals, however, that it is only the changeCount that is updated when the app is reactivated.
I dealt with this by tracking the pasteboard's changeCount in my app delegate and posting the expected notification when I find the changeCount has been changed while the app was in the background.
In the app delegate's interface:
NSUInteger pasteboardChangeCount_;
And in the app delegate's implementation:
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(pasteboardChangedNotification:)
name:UIPasteboardChangedNotification
object:[UIPasteboard generalPasteboard]];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(pasteboardChangedNotification:)
name:UIPasteboardRemovedNotification
object:[UIPasteboard generalPasteboard]];
...
}
- (void)pasteboardChangedNotification:(NSNotification*)notification {
pasteboardChangeCount_ = [UIPasteboard generalPasteboard].changeCount;
}
- (void)applicationDidBecomeActive:(UIApplication*)application {
if (pasteboardChangeCount_ != [UIPasteboard generalPasteboard].changeCount) {
[[NSNotificationCenter defaultCenter]
postNotificationName:UIPasteboardChangedNotification
object:[UIPasteboard generalPasteboard]];
}
}