swift - macOS 上的 kIOPSCurrentCapacityKey 是否有等效的电池电量更改通知?

标签 swift macos cocoa iokit batterylevel

我正在构建一个 Swift 应用程序,用于监控 Mac 笔记本电脑电池的电池百分比和充电状态。在 iOS 上,有一个 batteryLevelDidChange 通知,当设备的电池百分比发生变化时发送,以及一个 batteryStateDidChange 通知,当设备插入、拔出时发送,并充满电。

Swift 中这两个通知在 macOS 中的等效项是什么,或者更具体地说,对于 kIOPSCurrentCapacityKeykIOPSISChargingKey?我通读了通知文档,但没有看到任何通知。这是我获取当前电池电量和充电状态的代码:

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 notification”中出现的第三个结果。)

您要查找的函数是 IOPSNotificationCreateRunLoopSourceIOPSCreateLimitedPowerNotification

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")
    }
}

归功于 this post and Koen.'s answer .

关于swift - macOS 上的 kIOPSCurrentCapacityKey 是否有等效的电池电量更改通知?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51275093/

相关文章:

swift - 在 Swift 中声明随机数量的对象

ios - 如何仅在单元格中禁用prepareForSegue 和DidSelectRow?

objective-c - 制作自定义 NSMenuItem View 时如何反转 NSImageNameMenuOnStateTemplate 图像?

python - 如何从操作系统环境变量创建字典?

objective-c - 系统偏好设置面板(安装)

objective-c - 为什么我在将多个 QTCaptureVideoPreviewOutputs 与单个 QTCaptureSession 一起使用时会遇到低帧率?

swift - 无法用@objc 标记方法,参数无法在 Objective-C 中表示

ios - 如何禁用除所选单元格之外的所有单元格

xcode - 无法在 OS X 中加载内核扩展(.kext 文件)(不包含架构 :x86_64 error) 的代码)

python - 无法在 Mac OSX 中通过 Python 将 Skype4Py 连接到 Skype