[英]How to have an event-loop on non-main thread in macOS?
与另一个问题相关:我需要收集有关 macOS 上当前活动应用程序的信息。
链接的 QA 答案提供了一种机制,可在活动应用程序更改时收到警报(事件),但在单独的线程上运行时会崩溃:
FocusDetector::AppFocus focus;
focus.run();
//std::thread threadListener(&FocusDetector::AppFocus::run, &focus); //Does not works
//if (threadListener.joinable())
//{
// threadListener.join();
//}
.
*** Assertion failure in +[NSUndoManager _endTopLevelGroupings], /xxxxxxx/NSUndoManager.m:363
2020-11-24 08:54:41.758 focus_detection[81935:18248374] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+[NSUndoManager(NSInternal) _endTopLevelGroupings] is only safe to invoke on the main thread.'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff3006cb57 __exceptionPreprocess + 250
1 libobjc.A.dylib 0x00007fff68eb35bf objc_exception_throw + 48
2 CoreFoundation 0x00007fff30095d08 +[NSException raise:format:arguments:] + 88
3 Foundation 0x00007fff32787e9d -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191
4 Foundation 0x00007fff326c45ee +[NSUndoManager(NSPrivate) _endTopLevelGroupings] + 440
5 AppKit 0x00007fff2d25165c -[NSApplication run] + 864
6 focus_detection 0x0000000104b1a010 _ZN13FocusDetector8AppFocus3runEv + 128
7 focus_detection 0x0000000104b19547 _ZNSt3__1L8__invokeIMN13FocusDetector8AppFocusEFvvEPS2_JEvEEDTcldsdeclsr3std3__1E7forwardIT0_Efp0_Efp_spclsr3std3__1E7forwardIT1_Efp1_EEEOT_OS6_DpOS7_ + 119
8 focus_detection 0x0000000104b1944e _ZNSt3__1L16__thread_executeINS_10unique_ptrINS_15__thread_structENS_14default_deleteIS2_EEEEMN13FocusDetector8AppFocusEFvvEJPS7_EJLm2EEEEvRNS_5tupleIJT_T0_DpT1_EEENS_15__tuple_indicesIJXspT2_EEEE + 62
9 focus_detection 0x0000000104b18c66 _ZNSt3__114__thread_proxyINS_5tupleIJNS_10unique_ptrINS_15__thread_structENS_14default_deleteIS3_EEEEMN13FocusDetector8AppFocusEFvvEPS8_EEEEEPvSD_ + 118
10 libsystem_pthread.dylib 0x00007fff6a260109 _pthread_start + 148
11 libsystem_pthread.dylib 0x00007fff6a25bb8b thread_start + 15
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Abort trap: 6
这显然与NSApplication
相关,文档说明:
每个应用程序都使用 NSApplication 的单个实例来控制主事件循环
因此,我正在寻找另一种侦听事件的方法,它不限于主事件循环(或主线程。
直观地说,应该可以在单独的线程中获取有关当前应用程序的焦点信息。
我不知道如何解决这个问题,抱歉没有提供太多研究。 我确实在互联网上研究过“NSNotification not in main thread”和其他类似的句子,但没有成功。
如何在主线程外监听activeAppDidChange
NSNotification?
将观察者置于以下通知中,让他们调用您的方法。 NotificationCenter 是单例的,这是有充分理由的,而您当然可以创建自己的。 我猜[NSWorkspace sharedWorkspace]
是一个单例,而[[NSWorkspace sharedWorkspace] notificationCenter]
命名为 - 要观察的通知中心。 在您的情况下,期望的发送对象nil
,因为您不知道要更具体地观察哪个对象。
#import <AppKit/AppKit.h>
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[[[NSWorkspace sharedWorkspace] notificationCenter]
addObserver:self
selector:@selector(activeAppDidChange:)
name:NSWorkspaceDidActivateApplicationNotification
object:nil
];
[[[NSWorkspace sharedWorkspace] notificationCenter]
addObserver:self
selector:@selector(activeAppDidTerminate:)
name:NSWorkspaceDidTerminateApplicationNotification
object:nil
];
// id<NSObject> myObserver;
_myObserver = [[[NSWorkspace sharedWorkspace] notificationCenter]
addObserverForName:NSWorkspaceDidHideApplicationNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * _Nonnull note) {
// do stuff in block
NSRunningApplication *app = note.userInfo[NSWorkspaceApplicationKey];
NSLog(@"%u %@ %ld",
app.processIdentifier,
app.bundleIdentifier,
(long)app.activationPolicy
);
}
];
}
-(void)activeAppDidChange:(NSNotification *)note {
NSLog(@"%@",note.userInfo.debugDescription);
}
-(void)activeAppDidTerminate:(NSNotification *)note {
NSLog(@"%@",note.userInfo.debugDescription);
}
只是证明您可以在不是 mainThread 的线程上收到通知。
// ThreadManagerExample.h
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ThreadManagerExample : NSObject
@property (nonatomic, readonly) BOOL started;
@property (nonatomic, readonly) uint64_t looptime;
-(void)start;
-(void)stop;
@end
NS_ASSUME_NONNULL_END
// ThreadManagerExample.m
#import "ThreadManagerExample.h"
static const double kThreadPriority = 1.0;
@interface ReceivingThread : NSThread
@property (nonatomic, weak) ThreadManagerExample * threadManager;
@end
@interface ThreadManagerExample ()
@property (nonatomic, strong) ReceivingThread *thread;
@property (nonatomic, readwrite) BOOL started;
@property (nonatomic, readwrite) uint64_t looptime;
@end
@implementation ThreadManagerExample
-(void)dealloc {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(startThread) object:nil];
if ( _thread ) {
[_thread cancel];
while ( !_thread.isFinished ) {
[NSThread sleepForTimeInterval:0.01];
}
}
}
-(instancetype)init {
if ( !(self = [super init]) ) return nil;
_looptime = 1000000000; // 1 sec
return self;
}
-(void)startThread {
if ( !_thread) {
self.thread = [ReceivingThread new];
_thread.threadManager = self;
_thread.name=@"ReceivingThread";
[_thread setThreadPriority:kThreadPriority];
[_thread start];
}
}
-(void)start {
if ( !_thread ) {
@synchronized ( self ) {
self.started = YES;
}
[self performSelector:@selector(startThread) withObject:nil afterDelay:0.0];
}
}
-(void)stop {
@synchronized ( self ) {
self.started = NO;
}
if ( _thread ) {
[_thread cancel];
self.thread = nil;
}
}
@end
@implementation ReceivingThread {
BOOL somethinghappend;
NSString *oldBundleIdentifier;
NSString *lastbundleIdentifier;
pid_t oldPID;
pid_t focusedPID;
}
-(instancetype)init {
if (!(self=[super init])) return nil;
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(mimi:) name:NSWorkspaceDidActivateApplicationNotification object:nil];
return self;
}
-(void)mimi:(NSNotification*)note {
NSRunningApplication *app = note.userInfo[NSWorkspaceApplicationKey];
lastbundleIdentifier = app.bundleIdentifier;
focusedPID = app.processIdentifier;
if (![oldBundleIdentifier isEqualToString:lastbundleIdentifier]) {
somethinghappend = YES;
}
oldBundleIdentifier = lastbundleIdentifier;
oldPID = focusedPID;
}
-(void)main {
[NSThread setThreadPriority:kThreadPriority];
while ( !self.isCancelled ) {
uint64_t nextLoop = 0;
@synchronized ( _threadManager ) {
if (somethinghappend) {
NSLog(@"%@ %u",lastbundleIdentifier, focusedPID);
somethinghappend = NO;
}
uint64_t now = mach_absolute_time();
nextLoop = now + _threadManager.looptime;
}
mach_wait_until(nextLoop);
}
}
@end
和实例和启动是这样完成的..
if (!_manager) _manager = [[ThreadManagerExample alloc] init];
[_manager start];
停止
[_manager stop];
_manager = nil; // if needed
因此,当循环时间设置为 1 秒时,它会每秒检查最后收到的 bundleIdentifier 是否已更改。 您可以通过检查 bundleIdentifier 是否与[[NSBundle mainBundle] bundleIdentifier]
不同来扩展-(void)mimi:
方法,以了解您是否自己变得活跃并在需要时忽略它。
从Apple Docs编辑
NSRunningApplication
是线程安全的,因为它的属性是原子返回的。 但是,它仍然受上述主运行循环策略的约束。 如果您从后台线程访问 NSRunningApplication 的实例,请注意,随着主运行循环运行(或不运行),其随时间变化的属性可能会从您的下方改变。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.