在OSX 10.6+中识别当前活动应用程序的"正确"方法是什么?

dun*_*anm 22 cocoa objective-c

我正在尝试确定哪个OSX应用程序当前处于活动状态.据我所知,在OSX 10.5中,可以通过以下方式完成:

[[NSWorkspace sharedWorkspace] activeApplication]
Run Code Online (Sandbox Code Playgroud)

但是,这已经在10.6+中被弃用了.

Apple开发人员文档指出,这应该通过NSRunningApplication对象的"active"属性来完成.我认为解决这个问题的一种方法可能是获取所有正在运行的应用程序的列表

[[NSWorkspace sharedWorkspace] runningApplications]
Run Code Online (Sandbox Code Playgroud)

然后循环,检查每个应用程序的"活动"属性.但是,以下测试代码的行为与我的预期不同:从Terminal.app编译和运行时,无论是否选择其他应用程序,只有"终端"应用程序被标记为活动状态.

#import <Foundation/Foundation.h>
#import <AppKit/NSRunningApplication.h>
#import <AppKit/NSWorkspace.h>

int main(int argc, char *argv[]) {
  while(1){
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    NSString *currApp;
    NSArray *runningApps;
    runningApps = [[NSWorkspace sharedWorkspace] runningApplications];
    for (id currApp in runningApps) {
      if ([currApp isActive])
          NSLog(@"* %@", [currApp localizedName]);
      else
          NSLog(@"  %@", [currApp localizedName]);
    }
    sleep(1);
    [pool release];
  }

  return 0;
}
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?我误解了"活跃"财产是如何运作的吗?

(另外,请随意批评我的Objective C代码---这是我在目标C上的第一次尝试,所以我知道它很可能对训练有素的眼睛很难看!请原谅我!:)欢迎任何建议.)

Lil*_*ard 20

您的问题是您的应用程序无法从系统接收任何事件,通知它当前应用程序已更改,因此它永远不会更新NSRunningApplication实例上的活动属性.如果我使用完全相同的代码,但是当我开始运行代码时,另一个应用程序处于活动状态,它会报告该应用程序.

相反,如果您更改代码以运行主线程NSRunLoop并使用1秒计时器,它应该工作.

这是一个简单的例子:

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>

@interface Foo : NSObject
- (void)run;
@end

@implementation Foo
- (void)run {
    for (NSRunningApplication *currApp in [[NSWorkspace sharedWorkspace] runningApplications]) {
        if ([currApp isActive]) {
            NSLog(@"* %@", [currApp localizedName]);
        } else {
            NSLog(@"  %@", [currApp localizedName]);
        }
    }
    NSLog(@"---");
}
@end

int main(int argc, char *argv[]) {
    NSAutoreleasePool *p = [NSAutoreleasePool new];

    Foo *foo = [[Foo new] autorelease];
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0f
                                                      target:foo
                                                    selector:@selector(run)
                                                    userInfo:nil
                                                     repeats:YES];
    [[NSRunLoop mainRunLoop] run];

    [p release];
}
Run Code Online (Sandbox Code Playgroud)

  • @duncanm:重点是你需要运行主`NSRunLoop`,你需要让控制返回到运行循环.我刚刚用一个确实如此的例子更新了我的答案.它使用`NSTimer`来调用该方法,并且该方法本身仅迭代应用程序一次并返回.这样控制就会返回到runloop,因此它可以接收事件,1秒后它再次调用该方法. (2认同)

NSG*_*God 17

每隔一秒左右轮询以查找当前的应用程序是低效的,并且这是错误的方法.更好的方法是简单地设置您的流程以接收NSWorkspaceDidActivateApplicationNotification通知.

@interface MDAppController : NSObject <NSApplicationDelegate> {
    NSRunningApplication    *currentApp;
}
@property (retain) NSRunningApplication *currentApp;
@end

@implementation MDAppController 
@synthesize currentApp;

- (id)init {
    if ((self = [super init])) {
        [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
                      selector:@selector(activeAppDidChange:)
               name:NSWorkspaceDidActivateApplicationNotification object:nil];
    }
    return self;
}
- (void)dealloc {
    [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
    [super dealloc];
}
- (void)activeAppDidChange:(NSNotification *)notification {
    self.currentApp = [[notification userInfo] objectForKey:NSWorkspaceApplicationKey];
    NSLog(@"currentApp == %@", currentApp);
}
@end

int main(int argc, const char * argv[]) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    [NSApplication sharedApplication];
    MDAppController *appController = [[MDAppController alloc] init];
    [NSApp setDelegate:appController];
    [NSApp run];
    [pool release];
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • 我相信`currentApplication`会返回代表运行代码的应用程序的`NSRunningApplication`,而不是UI中最重要的应用程序. (4认同)

cod*_*nd1 12

从OS X 10.7开始NSWorkspace也有方便的方法:

- (NSRunningApplication *)frontmostApplication;
Run Code Online (Sandbox Code Playgroud)

此外,您现在可以使用Grand Central调度调用以超时进行重复调用.

像这样的东西:

- (void) checkFrontmostApp {

    double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        NSRunningApplication* runningApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
        //do something
        NSLog(@"frontmost app: %@", runningApp.bundleIdentifier);
        [self checkFrontmostApp]; //'recursive' call
    }); 
}
Run Code Online (Sandbox Code Playgroud)