如何在 Swift 中实现 IOServiceMatching CallBack

How to implement IOServiceMatchingCallBack in Swift

我想检测在我的应用程序中插入了特定的 USB in/removed。现在,我可以通过本教程 Working With USB Device Interfaces 获取设备名称。但是,如何在 Swift.

中执行 (deviceAdded)IOServiceMatchingCallBack 的回调函数

我尝试如下,但出现错误:无法将类型“(UnsafePointer, iterator: io_iterator_t) -> ()”的值转换为预期的参数类型'IOServiceMatchingCallback!'

func detectUSBEvent() {
    var portIterator: io_iterator_t = 0
    var kr: kern_return_t = KERN_FAILURE
    let matchingDict = IOServiceMatching(kIOUSBDeviceClassName)

    let vendorIDString = kUSBVendorID as CFStringRef!
    let productIDString = kUSBProductID as CFStringRef!
    CFDictionarySetValue(matchingDict, unsafeAddressOf(vendorIDString), unsafeAddressOf(VendorID))
    CFDictionarySetValue(matchingDict, unsafeAddressOf(productIDString), unsafeAddressOf(ProductID))

    // To set up asynchronous notifications, create a notification port and add its run loop event source to the program’s run loop
    let gNotifyPort: IONotificationPortRef = IONotificationPortCreate(kIOMasterPortDefault)
    let runLoopSource: Unmanaged<CFRunLoopSource>! = IONotificationPortGetRunLoopSource(gNotifyPort)
    let gRunLoop: CFRunLoop! = CFRunLoopGetCurrent()

    CFRunLoopAddSource(gRunLoop, runLoopSource.takeUnretainedValue(), kCFRunLoopDefaultMode)

    // Notification of first match:
    kr = IOServiceAddMatchingNotification(gNotifyPort, kIOFirstMatchNotification, matchingDict, deviceAdded, nil, &portIterator)
    deviceAdded(nil, iterator: portIterator)
 }


func deviceAdded(refCon: UnsafePointer<Void>, iterator: io_iterator_t) {
    if let usbDevice: io_service_t = IOIteratorNext(iterator)
    {
        let name = String()
        let cs = (name as NSString).UTF8String
        let deviceName: UnsafeMutablePointer<Int8> = UnsafeMutablePointer<Int8>(cs)

        let kr: kern_return_t = IORegistryEntryGetName(usbDevice, deviceName)
        if kr == KERN_SUCCESS {
            let deviceNameAsCFString = CFStringCreateWithCString(kCFAllocatorDefault, deviceName,
                kCFStringEncodingASCII)
            print(deviceNameAsCFString)
            // if deviceNameAsCFString == XXX
            // Do Something
        }

    }

}

在我将回调函数放在 class 之后 有效 。但是,我不知道为什么。

class IODetection {
    class func monitorUSBEvent(VendorID: Int, ProductID: Int) {


        var portIterator: io_iterator_t = 0
        var kr: kern_return_t = KERN_FAILURE
        let matchingDict = IOServiceMatching(kIOUSBDeviceClassName)

        // Add the VENDOR and PRODUCT IDs to the matching dictionary.
        let vendorIDString = kUSBVendorID as CFStringRef!
        let productIDString = kUSBProductID as CFStringRef!
        CFDictionarySetValue(matchingDict, unsafeAddressOf(vendorIDString), unsafeAddressOf(VendorID))
        CFDictionarySetValue(matchingDict, unsafeAddressOf(productIDString), unsafeAddressOf(ProductID))

        // To set up asynchronous notifications, create a notification port and add its run loop event source to the program’s run loop
        let gNotifyPort: IONotificationPortRef = IONotificationPortCreate(kIOMasterPortDefault)
        let runLoopSource: Unmanaged<CFRunLoopSource>! = IONotificationPortGetRunLoopSource(gNotifyPort)
        let gRunLoop: CFRunLoop! = CFRunLoopGetCurrent()

        CFRunLoopAddSource(gRunLoop, runLoopSource.takeRetainedValue(), kCFRunLoopDefaultMode)

        // MARK: - USB in Notification
        let observer = UnsafeMutablePointer<Void>(unsafeAddressOf(self))
        kr = IOServiceAddMatchingNotification(gNotifyPort,
                                              kIOMatchedNotification,
                                              matchingDict,
                                              deviceAdded,
                                              observer,
                                              &portIterator)
        deviceAdded(nil, iterator: portIterator)


        // MARK: - USB remove Notification
        kr = IOServiceAddMatchingNotification(gNotifyPort,
                                              kIOTerminatedNotification,
                                              matchingDict,
                                              deviceRemoved,
                                              observer,
                                              &portIterator)
        deviceRemoved(nil, iterator: portIterator)

    }
}

func deviceAdded(refCon: UnsafeMutablePointer<Void>, iterator: io_iterator_t) -> Void {
    var kr: kern_return_t = KERN_FAILURE

    while case let usbDevice = IOIteratorNext(iterator) where usbDevice != 0 {
        let deviceNameAsCFString = UnsafeMutablePointer<io_name_t>.alloc(1)
        defer {deviceNameAsCFString.dealloc(1)}
        kr = IORegistryEntryGetName(usbDevice, UnsafeMutablePointer(deviceNameAsCFString))
        if kr != KERN_SUCCESS {
            deviceNameAsCFString.memory.0 = 0
        }
        let deviceName = String.fromCString(UnsafePointer(deviceNameAsCFString))
        print("Device Added: \(deviceName!)")

        // Do something if I get the specific device
        if deviceName == "YOUR DEVICE" {
            /// Your Action HERE
        }

        IOObjectRelease(usbDevice)
    }
}

这是一个 Swift 3 版本,使用闭包而不是全局函数(闭包 w/o 上下文可以桥接到 C 函数指针),使用 GCD 而不是 Runloops(更好 API),使用回调和调度来通知事件并使用真实对象而不是静态对象或单例:

import Darwin
import IOKit
import IOKit.usb
import Foundation


class IOUSBDetector {

    enum Event {
        case Matched
        case Terminated
    }

    let vendorID: Int
    let productID: Int

    var callbackQueue: DispatchQueue?

    var callback: (
        ( _ detector: IOUSBDetector,  _ event: Event,
            _ service: io_service_t
        ) -> Void
    )?


    private
    let internalQueue: DispatchQueue

    private
    let notifyPort: IONotificationPortRef

    private
    var matchedIterator: io_iterator_t = 0

    private
    var terminatedIterator: io_iterator_t = 0


    private
    func dispatchEvent (
        event: Event, iterator: io_iterator_t
    ) {
        repeat {
            let nextService = IOIteratorNext(iterator)
            guard nextService != 0 else { break }
            if let cb = self.callback, let q = self.callbackQueue {
                q.async {
                    cb(self, event, nextService)
                    IOObjectRelease(nextService)
                }
            } else {
                IOObjectRelease(nextService)
            }
        } while (true)
    }


    init? ( vendorID: Int, productID: Int ) {
        self.vendorID = vendorID
        self.productID = productID
        self.internalQueue = DispatchQueue(label: "IODetector")

        guard let notifyPort = IONotificationPortCreate(kIOMasterPortDefault) else {
            return nil
        }

        self.notifyPort = notifyPort
        IONotificationPortSetDispatchQueue(notifyPort, self.internalQueue)
    }

    deinit {
        self.stopDetection()
    }


    func startDetection ( ) -> Bool {
        guard matchedIterator == 0 else { return true }

        let matchingDict = IOServiceMatching(kIOUSBDeviceClassName)
            as NSMutableDictionary
        matchingDict[kUSBVendorID] = NSNumber(value: vendorID)
        matchingDict[kUSBProductID] = NSNumber(value: productID)

        let matchCallback: IOServiceMatchingCallback = {
            (userData, iterator) in
                let detector = Unmanaged<IOUSBDetector>
                    .fromOpaque(userData!).takeUnretainedValue()
                detector.dispatchEvent(
                    event: .Matched, iterator: iterator
                )
        };
        let termCallback: IOServiceMatchingCallback = {
            (userData, iterator) in
                let detector = Unmanaged<IOUSBDetector>
                    .fromOpaque(userData!).takeUnretainedValue()
                detector.dispatchEvent(
                    event: .Terminated, iterator: iterator
                )
        };

        let selfPtr = Unmanaged.passUnretained(self).toOpaque()

        let addMatchError = IOServiceAddMatchingNotification(
            self.notifyPort, kIOFirstMatchNotification,
            matchingDict, matchCallback, selfPtr, &self.matchedIterator
        )
        let addTermError = IOServiceAddMatchingNotification(
            self.notifyPort, kIOTerminatedNotification,
            matchingDict, termCallback, selfPtr, &self.terminatedIterator
        )

        guard addMatchError == 0 && addTermError == 0 else {
            if self.matchedIterator != 0 {
                IOObjectRelease(self.matchedIterator)
                self.matchedIterator = 0
            }
            if self.terminatedIterator != 0 {
                IOObjectRelease(self.terminatedIterator)
                self.terminatedIterator = 0
            }
            return false
        }

        // This is required even if nothing was found to "arm" the callback
        self.dispatchEvent(event: .Matched, iterator: self.matchedIterator)
        self.dispatchEvent(event: .Terminated, iterator: self.terminatedIterator)

        return true
    }


    func stopDetection ( ) {
        guard self.matchedIterator != 0 else { return }
        IOObjectRelease(self.matchedIterator)
        IOObjectRelease(self.terminatedIterator)
        self.matchedIterator = 0
        self.terminatedIterator = 0
    }
}

下面是一些简单的测试代码,用于测试 class(根据您的 USB 设备设置产品和供应商 ID):

let test = IOUSBDetector(vendorID: 0x4e8, productID: 0x1a23)
test?.callbackQueue = DispatchQueue.global()
test?.callback = {
    (detector, event, service) in
        print("Event \(event)")
};
_ = test?.startDetection()
while true { sleep(1) }

我的问题是我没有在我的回调函数中使用迭代器,所以那个函数甚至没有被调用!对我来说似乎是奇怪的行为,但那是我的问题。