Изменился ли уровень заряда батареи, изменивший эквивалент уведомления для kIOPSCurrentCapacityKey на macOS?

Я создаю приложение Swift, которое отслеживает процент заряда батареи, а также состояние зарядки батареи ноутбука Mac. На iOS есть batteryLevelDidChange уведомление, которое отправляется при изменении процента заряда батареи устройства, а также batteryStateDidChange уведомление, которое отправляется, когда устройство подключено, отключено и полностью заряжено.

Что такое macOS-эквивалент этих двух уведомлений в Swift или, более конкретно, для 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()
}
}

2 ответа

(Я отвечаю на этот вопрос почти трехлетней давности, поскольку это третий результат, который появляется в поиске Google «быстрое уведомление iokit».)

Функции, которые вы ищете, - это и IOPSCreateLimitedPowerNotification.

Простейшее использование IOPSNotificationCreateRunLoopSource:

      import IOKit

let loop = IOPSNotificationCreateRunLoopSource({ _ in
    // Perform usual battery status fetching
}, nil).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, .defaultMode)

Обратите внимание, что второй параметр contextпередается как единственный параметр в функции обратного вызова, который можно использовать для передачи экземпляра в качестве указателя на закрытие, поскольку функции C не захватывают контекст. (См. Ссылку ниже для фактической реализации.)

Вот мой код, который преобразует API в стиле C в более удобный для Swift, используя шаблон наблюдателя: (не знаю, какое преимущество в производительности он будет иметь для удаления циклов выполнения)

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

Кредиты на этот пост и ответ Коэна .

Я бы использовал эту ссылку, чтобы получить процент (выглядит чище) Получить состояние батареи моего MacBook с Swift

И чтобы найти изменения в состоянии, используйте timer повторное объявление состояния батареи каждые 5 секунд, а затем установка его в качестве новой переменной var OldBattery:Int повторно объявите это и установите это как NewBatteryзатем напишите этот код:

if (OldBattery =! NewBattery) {
      print("battery changed!")
      // write the function you want to happen here
}
Другие вопросы по тегам