可靠地检测计算机是否要进入睡眠状态

时间:2018-07-10 22:07:51

标签: swift macos cocoa

我们正在为Mac编写一个Swift应用程序,其中我们需要检测计算机的睡眠时间戳。


我们发现NSWorkspace.willSleepNotificationNSWorkspace.didWakeNotification不够可靠,因为在极少数情况下,NSWorkspace.willSleepNotification仅在PC唤醒并能够处理当前堆栈后才被调用。您还有其他建议吗?

2 个答案:

答案 0 :(得分:2)

您可以向系统电源管理器注册回调(请参见IORegisterForSystemPower)。当系统要进入睡眠状态(可以建议不要),即将进入睡眠状态以及再次唤醒时,将调用回调函数。

此Objective-C包装器由Philip Dow于十年前贡献,至今仍能正常工作。这些年来,我可能已经做了些微修改,因此可以针对最新的OS和Xcode进行编译,但是在其他方面则保持不变。 (注意:原始网站已不存在,因此我将其粘贴在此处。)在Swift中重写,桥接到Obj-C代码或仅以使用{ {1}}并编写自己的回调函数(如果需要确切的时间)。

要使用Philip的代码,只需调用IORegisterForSystemPower创建单例管理器,然后在通知中心注册观察者以接收+sharedPowerManagement通知。还有一个委托使用模型。

PDPowerManagementNotification

//
//  PDPowerManagement.h
//  Originally part of the Journler project: http://journler.phildow.net
//  Source code available at http://developers.phildow.net
//
//  Created by Philip Dow on 3/21/06.
//  Licensed under the LGPL: http://www.gnu.org/copyleft/lesser.html
//  You may modify and redistribute the code as needed.
//  Please keep this original notice intact.
//
//  Of course, I would appreciate a mentioning in your app's about box.
//  If you make improvements or additions to the code, please let me know.
//

#import <Cocoa/Cocoa.h>

//
// Be sure to include the IOKit Framework in your project

#import <mach/mach_port.h>
#import <mach/mach_interface.h>
#import <mach/mach_init.h>

#import <IOKit/pwr_mgt/IOPMLib.h>
#import <IOKit/IOMessage.h>

//
// Notifications
//
// The PDPowerManagementNotification will be sent to the default notification center
// with the shared instance of PDPowerManagement as the object. To make sure that a shared
// instance is available, call [PDPowerManagement sharedPowerManagement] somewhere in your code.
//
// The notification's user info dictionary will contain the PDPowerManagementMessage key with an
// NSNumber whose int value is either PDPowerManagementWillSleep or PDPowerManagementPoweredOn.

#define PDPowerManagementNotification   @"PDPowerManagementNotification"
#define PDPowerManagementMessage        @"PDPowerManagementMessage"
#define PDPowerManagementWillSleep      1
#define PDPowerManagementPoweredOn      3

//
// Disallowing Sleep
//
// There are two ways to disallow a power down. Either call setPermitSleep: with NO
// or implement the - (BOOL) shouldAllowIdleSleep:(id)sender delegate method and return NO as needed.
// At initialization _permitSleep is set to YES. With this value, the delegate method is
// always called if the delegate implements it. If _permitSleep is set to NO, the delegate
// method is never called. setPermitSleep: is thus a lazy way of always disallowing sleep.
//
// It must however be noted that it is not possible to cancel a sleep command that the user
// initiates. _permitSleep and the delegate method can only prevent an idle sleep. For
// more information: http://developer.apple.com/qa/qa2004/qa1340.html

@interface PDPowerManagement : NSObject {

    BOOL    _permitSleep;
    id      _delegate;

}

+ (id)sharedPowerManagement;

- (BOOL) permitSleep;
- (void) setPermitSleep:(BOOL)permitSleep;

- (id) delegate;
- (void) setDelegate:(id)delegate;

- (void) _postPMNotification:(NSInteger)message;
- (BOOL) _shouldAllowSleep;

@end

//
// Delegation
// You should implement: - (BOOL) shouldAllowIdleSleep:(id)sender
//
// If you set a delegate, before the computer is put to idle sleep the delegate's
// shouldAllowSleep: method will be called. Return NO to disallow the power down,
// return yes to permit it.

@interface NSObject (PDPowerManagementDelegate)

//
// return YES to permit a power down, NO to disallow it
- (BOOL) shouldAllowIdleSleep:(id)sender;

@end

答案 1 :(得分:2)

查看了10.14.2上的AppKit的反汇编之后,我发现NSWorkspace.willSleepNotification是在较低级别的IOKit IORegisterForSystemPower API之上实现的。因此,对于大多数用例而言,使用更高级别的AppKit API就足够了。

一个陷阱是,AppKit将使用CFRunLoopPerformBlockkCFRunLoopCommonModes将通知的发布放入主运行循环中。因此,如果您的主线程被阻塞,或者如果主运行循环当前正在非常见模式之一下运行,则通知的发布将被延迟。但是,电源管理系统会给每个进程30秒钟来响应通知,因此应用程序被阻塞的可能性很小,以至于它可能在计算机进入睡眠状态之前错过通知。 (如果可能发生 ,则应重新构建该应用程序,以使其始终不阻塞主线程。)