How to implement IOServiceMatchingCallBack in Swift

前端 未结 3 681
执念已碎
执念已碎 2021-01-15 12:08

I would like to detect a specific USB is plugged in/removed in my application. For now, I can get the deviceName with this tutorial Working With USB Device Interfaces. But,

相关标签:
3条回答
  • 2021-01-15 13:02

    It works after I put the callback function out the class. However, I don't know why.

    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)
        }
    }
    
    0 讨论(0)
  • 2021-01-15 13:04

    Here's a Swift 3 version, using closures instead of global functions (a closure w/o a context can be bridged to a C function pointer), using GCD instead of Runloops (much nicer API), using callbacks and dispatches to inform about events and using real objects instead of static objects or singletons:

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

    And here is some simple test code to test that class (set product and vendor ID as appropriate for your USB device):

    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) }
    
    0 讨论(0)
  • 2021-01-15 13:06

    Got this working thanks! Only problem was that I wasn't using the iterator in my callback function, so that function wasn't even getting called! Seems like strange behaviour to me, but that was my problem

    0 讨论(0)
提交回复
热议问题