MacOS 真实空闲睡眠检测问题:NSWorkspaceWillSleepNotification与IORegisterForSystemPower误触发及可靠检测方案咨询
首先要澄清你遇到的情况——这不是macOS的设计缺陷,而是系统睡眠机制的正常行为:当系统准备触发空闲睡眠时,会先向所有进程发送「即将睡眠」的预告通知(也就是你用到的NSWorkspaceWillSleepNotification和kIOMessageSystemWillSleep),但此时任何应用都可以通过持有睡眠断言(比如用NSWorkspace.disableAutomaticSleep()禁用自动睡眠,或者IO层面的IOPMAssertionCreateWithName创建kIOPMAssertionTypeNoIdleSleep类型断言)来阻止系统进入睡眠。这种情况下,系统会取消睡眠流程,但不会撤回已经发出的预告通知,所以你会收到通知但系统并没有真的入睡。
针对你的需求(仅检测实际发生的空闲睡眠),以下是几种可靠的方案:
1. 监听「睡眠完成」的通知(最直接可靠)
macOS提供了专门的通知,只有当系统真正进入睡眠状态后才会触发,完全避免了「预告但不执行」的问题:
Cocoa 层面(Swift/Objective-C)
监听NSWorkspace.didSleepNotification:
// Swift 示例 NotificationCenter.default.addObserver( forName: NSWorkspace.didSleepNotification, object: nil, queue: .main ) { _ in print("系统已进入空闲睡眠") // 在这里处理睡眠后的逻辑 }
IOKit 层面
在IORegisterForSystemPower的回调中监听kIOMessageSystemDidSleep消息:
// Objective-C 示例 #include <IOKit/pwr_mgt/IOPMLib.h> static io_connect_t gRootPort; static io_service_t gNotifyPort; static void *gRefCon; void powerChangeCallback(void *refCon, io_service_t service, natural_t messageType, void *messageArgument) { switch (messageType) { case kIOMessageSystemDidSleep: NSLog(@"系统已进入空闲睡眠"); break; case kIOMessageSystemDidWake: NSLog(@"系统已从睡眠唤醒"); break; // 其他消息类型可按需处理 } } // 注册监听 IOReturn result = IORegisterForSystemPower(&gRootPort, &gNotifyPort, powerChangeCallback, &gRefCon); if (result == kIOReturnSuccess) { CFRunLoopAddSource( CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(gNotifyPort), kCFRunLoopCommonModes ); }
2. 提前判断睡眠是否会真正发生(可选)
如果你需要在收到预告通知后,提前判断系统是否真的会进入空闲睡眠,可以结合以下步骤:
- 收到
NSWorkspaceWillSleepNotification或kIOMessageSystemWillSleep后,启动一个1-2秒的定时器(系统发送预告后会等待短时间让应用处理,之后才会尝试入睡)。 - 在定时器回调中,检查当前是否有应用持有阻止空闲睡眠的断言:
- 使用
IOPMCopyAssertionList获取所有活跃的电源断言,遍历查找类型为kIOPMAssertionTypeNoIdleSleep的断言。 - 或者直接检查
NSWorkspace.shared.isAutomaticSleepDisabled属性,判断自动睡眠是否被禁用。
- 使用
3. 结合显示器睡眠状态(辅助验证)
空闲睡眠通常会伴随显示器关闭,你可以通过监听显示器状态变化来辅助验证:
使用CGDisplayRegisterReconfigurationCallback注册显示器配置变更回调,当所有显示器都进入睡眠状态时,大概率系统即将进入空闲睡眠(注意:部分用户可能设置了显示器睡眠与系统睡眠的时间不同,所以这只是辅助手段)。
内容的提问来源于stack exchange,提问作者Oleg Korzhukov

