swift - macOS 上是否存在与 kIOPSCurrentCapacityKey 等效的电池电量更改通知?
问题描述
我正在构建一个 Swift 应用程序,用于监控 Mac 笔记本电脑电池的电池百分比以及充电状态。在 iOS 上,batteryLevelDidChange
当设备的电池百分比发生变化时会发送通知,以及batteryStateDidChange
在设备插入、拔出和充满电时发送通知。
Swift 中这两个通知的 macOS 等价物是什么,或者更具体地说,对于kIOPSCurrentCapacityKey
和kIOPSIsChargingKey
?我通读了通知文档,也没有看到任何通知。这是我用于获取当前电池电量和充电状态的代码:
import Cocoa
import IOKit.ps
class MainViewController: NSViewController {
enum BatteryError: Error { case error }
func getMacBatteryPercent() {
do {
guard let snapshot = IOPSCopyPowerSourcesInfo()?.takeRetainedValue()
else { throw BatteryError.error }
guard let sources: NSArray = IOPSCopyPowerSourcesList(snapshot)?.takeRetainedValue()
else { throw BatteryError.error }
for powerSource in sources {
guard let info: NSDictionary = IOPSGetPowerSourceDescription(snapshot, ps as CFTypeRef)?.takeUnretainedValue()
else { throw BatteryError.error }
if let name = info[kIOPSNameKey] as? String,
let state = info[kIOPSIsChargingKey] as? Bool,
let capacity = info[kIOPSCurrentCapacityKey] as? Int,
let max = info[kIOPSMaxCapacityKey] as? Int {
print("\(name): \(capacity) of \(max), \(state)")
}
}
} catch {
print("Unable to get mac battery percent.")
}
}
override func viewDidLoad() {
super.viewDidLoad()
getMacBatteryPercent()
}
}
解决方案
(我正在回答这个将近 3 年的问题,因为它是 Google 搜索“swift iokit 通知”中出现的第三个结果。)
您正在寻找的功能是IOPSNotificationCreateRunLoopSource
和IOPSCreateLimitedPowerNotification
。
最简单的用法IOPSNotificationCreateRunLoopSource
:
import IOKit
let loop = IOPSNotificationCreateRunLoopSource({ _ in
// Perform usual battery status fetching
}, nil).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, .defaultMode)
请注意,第二个参数context
作为回调函数中的唯一参数传递,可用于将实例作为指向闭包的指针传递,因为 C 函数不捕获上下文。(具体实现见下方链接。)
这是我的代码,它使用观察者模式将 C 风格的 API 转换为对 Swift 更友好的 API:(不知道它对删除运行循环有多大的性能优势)
import Cocoa
import IOKit
// Swift doesn't support nested protocol(?!)
protocol BatteryInfoObserverProtocol: AnyObject {
func batteryInfo(didChange info: BatteryInfo)
}
class BatteryInfo {
typealias ObserverProtocol = BatteryInfoObserverProtocol
struct Observation {
weak var observer: ObserverProtocol?
}
static let shared = BatteryInfo()
private init() {}
private var notificationSource: CFRunLoopSource?
var observers = [ObjectIdentifier: Observation]()
private func startNotificationSource() {
if notificationSource != nil {
stopNotificationSource()
}
notificationSource = IOPSNotificationCreateRunLoopSource({ _ in
BatteryInfo.shared.observers.forEach { (_, value) in
value.observer?.batteryInfo(didChange: BatteryInfo.shared)
}
}, nil).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), notificationSource, .defaultMode)
}
private func stopNotificationSource() {
guard let loop = notificationSource else { return }
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), loop, .defaultMode)
}
func addObserver(_ observer: ObserverProtocol) {
if observers.count == 0 {
startNotificationSource()
}
observers[ObjectIdentifier(observer)] = Observation(observer: observer)
}
func removeObserver(_ observer: ObserverProtocol) {
observers.removeValue(forKey: ObjectIdentifier(observer))
if observers.count == 0 {
stopNotificationSource()
}
}
// Functions for retrieving different properties in the battery description...
}
用法:
class MyBatteryObserver: BatteryInfo.ObserverProtocol {
init() {
BatteryInfo.shared.addObserver(self)
}
deinit {
BatteryInfo.shared.removeObserver(self)
}
func batteryInfo(didChange info: BatteryInfo) {
print("Changed")
}
}
归功于这篇文章和 Koen. 的回答。
推荐阅读
- php - Laravel - 用另一个查询过滤查询
- r - 如何从列索引而不是名称构建模型(见内)?
- mysql - 接口 jparepository 上的自定义方法
- c++ - 从文本文件读取到二维数组
- mongodb - 合并列具有相同值的文档并使用找到的数据创建字段
- python - 以列表为值的 Python 字典到 Pandas 数据框
- node.js - 为什么 firestore 文档既不创建也不更新数据?
- python - 如何在整个组级别上对多索引熊猫数据框进行排序,然后在组内排序?
- rust - 交叉编译后缺少共享库的替代解决方案?
- dialogflow-es - 对 Google 卡片轮播响应的操作:图片未显示