Создайте CFRunLoopSourceRef, используя IOPSNotificationCreateRunLoopSource в Swift
Я пытаюсь подписаться на изменения состояния питания в macOS. Я обнаружил, что есть способ использовать IOKit, хотя он немного запутан. Мне нужно импортировать его с помощью #import <IOKit/ps/IOPowerSources.h>
в заголовке моста ObjC. Затем я получаю доступ к функции IOPSNotificationCreateRunLoopSource, которая имеет подпись:
IOPSNotificationCreateRunLoopSource(_ callback: IOPowerSourceCallbackType!, _ context: UnsafeMutablePointer<Void>!) -> Unmanaged<CFRunLoopSource>!
Я получил некоторую помощь от ответа в методе обратного вызова на цикл выполнения Apple, но все еще не удается создать функцию типа IOPowerSourceCallbackType
в Свифте. Какова недостающая часть, чтобы иметь эту компиляцию?
1 ответ
Проблема в том, что IOPowerSourceCallbackType
является функцией C.
Согласно документации Apple, эти функции доступны как замыкания:
Указатели на функции C импортируются в Swift как замыкания с соглашением о вызове указателя на функцию C
Поэтому самый простой способ - использовать замыкание:
IOPSNotificationCreateRunLoopSource({ (context: UnsafeMutableRawPointer?) in
debugPrint("Power source changed")
}, &context)
Второй вариант - использовать функцию верхнего уровня:
func powerSourceChanged(arg: UnsafeMutableRawPointer?) {
debugPrint("Power source changed")
}
IOPSNotificationCreateRunLoopSource(powerSourceChanged, &context)
Для справки полная реализация того, как я использую это:
class WindowController: NSWindowController {
static var context = 0
override func windowDidLoad() {
super.windowDidLoad()
let loop: CFRunLoopSource = IOPSNotificationCreateRunLoopSource({ (context: UnsafeMutableRawPointer?) in
debugPrint("Power source changed")
}, &WindowController.context).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, CFRunLoopMode.defaultMode)
}
}
ОБНОВИТЬ
Чтобы позволить ему взаимодействовать с экземпляром, из которого был настроен цикл, вы должны передать self
как контекст, однако self
не указатель
Когда вы пытаетесь пройти self
в качестве указателя, добавив его с &
(&self
), вы получите ошибку, self
неизменен.
Чтобы преобразовать его в непрозрачный указатель (хотя и не совсем уверен, что это значит...), вы можете использовать Unmanaged
учебный класс:
let opaque = Unmanaged.passRetained(self).toOpaque()
Который затем может быть использован в качестве UnsafeMutableRawPointer
:
let context = UnsafeMutableRawPointer(opaque)
Что мы можем использовать в качестве контекста для IOPSNotificationCreateRunLoopSource
,
А затем в обратном вызове, используя Unmanaged
Снова класс, мы можем разрешить этот указатель обратно в его первоначальный экземпляр:
let opaque = Unmanaged<WindowController>.fromOpaque(context!)
let _self = opaque.takeRetainedValue()
Полный пример:
func PowerSourceChanged(context: UnsafeMutableRawPointer?) {
let opaque = Unmanaged<WindowController>.fromOpaque(context!)
let _self = opaque.takeRetainedValue()
_self.powerSourceChanged()
}
class WindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
let opaque = Unmanaged.passRetained(self).toOpaque()
let context = UnsafeMutableRawPointer(opaque)
let loop: CFRunLoopSource = IOPSNotificationCreateRunLoopSource(
PowerSourceChanged,
context
).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, CFRunLoopMode.defaultMode)
}
func powerSourceChanged() {
debugLog("Power source changed")
}
}
бонус
Соответствующую статью об указателях CFunction можно найти здесь:
http://nshint.io/blog/2015/10/10/working-with-cfunction-pointers-in-swift/