I created a simple app to learn how to use NSWorkspaceWillSleepNotification and NSWorkspaceDidWakeNotification. My goal is to call a method when the computer sleeps and wakes. The app I created will change each label accordingly. After building the app, I launch it from my desktop. After the application is launched, I put the computer to sleep. When the computer wakes the labels in the application do not change. I added IBAction buttons to the window to make sure that the labels would change. When buttons are pressed the labels do indeed change. But I want something like this to happen automatically upon sleep and wake. What am I doing wrong?
#import "Controller.h"
#implementation Controller
- (void)fileNotifications {
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: self
selector: #selector(makeSleep:)
name: NSWorkspaceWillSleepNotification
object: nil];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver: self
selector: #selector(makeWake:)
name: NSWorkspaceDidWakeNotification
object: nil];
}
- (IBAction)makeSleep:(id)sender {
[sleepLabel setStringValue:#"Computer Slept!"];
}
- (IBAction)makeWake:(id)sender {
[wakeLabel setStringValue:#"Computer Woke!"];
}
#end
Instead of [[NSWorkspace sharedWorkspace] notificationCenter] try using [NSNotificationCenter defaultCenter]
like this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(makeSleep:)
NSWorkspaceWillSleepNotification
object:nil
];
and
[[NSNotificationCenter defaultCenter] addObserver:self
#selector(makeWake:)
NSWorkspaceDidWakeNotification
object:nil
];
The above is incorrect, see https://developer.apple.com/library/mac/qa/qa1340/_index.html
Using [[NSWorkspace sharedWorkspace] notificationCenter] is necessary.
You should add observers upon - (void)awakeFromNib method.
Related
When setting up notification, you can set different selector to react to it. But there seems no way to remove notification by selector. For example:
// e.g. React to background notification by calling method 1
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(method1:) name:notification object:nil];
// e.g. React to background notification by calling method 2
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(method2:) name:notification object:nil];
Now when the notification fires, both methods will react to it.
How do I remove notification selectively (e.g. remove notification handler method1)?
There is a way to do it, but I don't think you will like it.
Use -addObserverForName:object:queue:usingBlock: instead.
__weak typeof(self) weakSelf = self;
// e.g. React to background notification by calling method 1
self.method1Observer = [[NSNotificationCenter defaultCenter] addObserverForName:notification object:nil queue:nil usingBlock:^(NSNotification *note) {
[weakSelf method1:note];
}];
// e.g. React to background notification by calling method 2
self.method2Observer = [[NSNotificationCenter defaultCenter] addObserverForName:notification object:nil queue:nil usingBlock:^(NSNotification *note) {
[weakSelf method2:note];
}];
Then later on:
// Remove method 1 observer while keeping method 2 observer.
if (self.method1Observer != nil) {
[[NSNotificationCenter defaultCenter] removeObserver:self.method1Observer];
self.method1Observer = nil;
}
Update: I forgot to nil check self.method1Observer before passing it to -removeObserver:.
you have to remove that notification when it will not necessary in that class. So simply use below code for remove already added Notification.
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"Notification" object:nil];
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.
I'm developing kind of a plugin for iTunes.
A lot of user have requested, that they would like to start the plugin if they start iTunes, which of course makes sense. However, I'm not sure how to do this.
I thought about a helper app, which is probably the only way.
The only thing that bothers me is how to get the notification.
Of course I could consistently check if iTunes is running, but I'm not sure if that's the right way to do it.
I would rather add my app as an observer of that process.
Is that possible?
If not, how does Activity Monitor do it?
SOLUTION
Thanks to Daij-Djan! I got it working like this:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
selector:#selector(iTunesLaunched:)
name:NSWorkspaceDidLaunchApplicationNotification
object:nil];
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
selector:#selector(iTunesTerminated:)
name:NSWorkspaceDidTerminateApplicationNotification
object:nil];
}
-(void) iTunesLaunched:(NSNotification *)notification {
NSRunningApplication *runApp = [[notification userInfo] valueForKey:#"NSWorkspaceApplicationKey"];
if ([runApp.bundleIdentifier isEqualToString:#"com.apple.iTunes"])
NSLog(#"start");
}
-(void) iTunesTerminated:(NSNotification *)notification {
NSRunningApplication *runApp = [[notification userInfo] valueForKey:#"NSWorkspaceApplicationKey"];
if ([runApp.bundleIdentifier isEqualToString:#"com.apple.iTunes"])
NSLog(#"terminate");
}
register for NSWorkspace notifications:
NSWorkspaceDidLaunchApplicationNotification
NSWorkspaceDidTerminateApplicationNotification
see https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSWorkspace_Class/Reference/Reference.html
there is also the possibility to KVO the runningApplications property
btw cocoatech has a nice NTRunningAppManager class that does just that
My problem is that the NSApplicationWillTerminateNotification is not called when I quit my application. What I have tried: (appDelegate.m)
- (void) applicationDidFinishLaunching:(NSNotification *)aNotification{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationWillTerminate:) name:NSApplicationWillTerminateNotification object:nil];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification{
NSLog(#"quit");
}
OR
- (void)applicationWillTerminate:(NSApplication *)application{
NSLog(#"Quit");
}
My application has no window, as it is a background application -> I deleted the window and the menu in interface Builder.
Apps in the background are terminated without any notification.
Settting up the observer code:
NSNotificationCenter *defaultCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
[defaultCenter addObserver:self
selector:#selector(updateLog:)
name:#"Update Log"
object:nil];
Sending the notification code:
[[NSNotificationCenter defaultCenter] postNotificationName:#"Update Log" object:self];
With the method defined as:
-(void)updateLog: (NSNotification *) notification {
NSLog(#"Update Log"); }
The text "Update Log" does not appear in the log when the notification is sent.
Thanks for any ideas for why this code is not working.
There is a difference between "the notification center for workspace notifications" Apple:
[[NSWorkspace sharedWorkspace] notificationCenter]
and "the process’s default notification center" Apple:
[NSNotificationCenter defaultCenter]
You need to pick one of those to use.