3

我正在尝试订阅 macOS 上的电源状态更改。我发现有一种使用 IOKit 的方法,虽然它有点复杂。我需要#import <IOKit/ps/IOPowerSources.h>在 ObjC Bridging 标头中使用它来导入它。然后我可以访问函数 IOPSNotificationCreateRunLoopSource,它具有以下签名:

IOPSNotificationCreateRunLoopSource(_ callback: IOPowerSourceCallbackType!, _ context: UnsafeMutablePointer<Void>!) -> Unmanaged<CFRunLoopSource>!

我从Callback method to Apple run loop的答案中得到了一些帮助,但仍然无法IOPowerSourceCallbackType在 Swift 中创建类型函数。这个编译缺少什么?

4

1 回答 1

3

问题在于这IOPowerSourceCallbackType是一个C 函数

根据 Apple 的文档,这些函数可作为闭包使用:

C 函数指针作为具有 C 函数指针调用约定的闭包导入 Swift

https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html#//apple_ref/doc/uid/TP40014216-CH8-ID148

所以最简单的方法是使用闭包:

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 指针的相关文章

于 2017-01-08T01:16:52.590 回答