Создайте 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

https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html

Поэтому самый простой способ - использовать замыкание:

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/

Другие вопросы по тегам