Swift上的USB连接代理

时间:2016-08-17 18:35:21

标签: swift macos usb

Swift中是否有代表可以让我的班级知道何时通过计算机的USB插入新设备?我想知道我的程序何时可以使用新设备。

2 个答案:

答案 0 :(得分:12)

Eric Aya的答案已经相当不错了,但这里有一个Swift 3改编版。我把大部分丑陋的东西包裹在USBWatcher班里;将自己设置为此对象的委托以接收通知。

您可以将以下内容复制/粘贴到游乐场以查看其是否正常工作 - 该示例仅在设备连接/断开连接时将消息记录到控制台。

令人遗憾的是,IOKit API没有获得与其他一些C API相同的Swift-ifying处理(例如CoreGraphics)。 io_name_t是一个笨重的元组而不是一个正确的结构,C结构的方式通常被导入Swift; io_object_t不是真正的参考类型,因此无法利用ARC。也许在将来这会发生变化 - 如果您希望看到更好的Swift API,您应该file an enhancement request

import Foundation
import IOKit
import IOKit.usb

public protocol USBWatcherDelegate: class {
    /// Called on the main thread when a device is connected.
    func deviceAdded(_ device: io_object_t)

    /// Called on the main thread when a device is disconnected.
    func deviceRemoved(_ device: io_object_t)
}

/// An object which observes USB devices added and removed from the system.
/// Abstracts away most of the ugliness of IOKit APIs.
public class USBWatcher {
    private weak var delegate: USBWatcherDelegate?
    private let notificationPort = IONotificationPortCreate(kIOMasterPortDefault)
    private var addedIterator: io_iterator_t = 0
    private var removedIterator: io_iterator_t = 0

    public init(delegate: USBWatcherDelegate) {
        self.delegate = delegate

        func handleNotification(instance: UnsafeMutableRawPointer?, _ iterator: io_iterator_t) {
            let watcher = Unmanaged<USBWatcher>.fromOpaque(instance!).takeUnretainedValue()
            let handler: ((io_iterator_t) -> Void)?
            switch iterator {
            case watcher.addedIterator: handler = watcher.delegate?.deviceAdded
            case watcher.removedIterator: handler = watcher.delegate?.deviceRemoved
            default: assertionFailure("received unexpected IOIterator"); return
            }
            while case let device = IOIteratorNext(iterator), device != IO_OBJECT_NULL {
                handler?(device)
                IOObjectRelease(device)
            }
        }

        let query = IOServiceMatching(kIOUSBDeviceClassName)
        let opaqueSelf = Unmanaged.passUnretained(self).toOpaque()

        // Watch for connected devices.
        IOServiceAddMatchingNotification(
            notificationPort, kIOMatchedNotification, query,
            handleNotification, opaqueSelf, &addedIterator)

        handleNotification(instance: opaqueSelf, addedIterator)

        // Watch for disconnected devices.
        IOServiceAddMatchingNotification(
            notificationPort, kIOTerminatedNotification, query,
            handleNotification, opaqueSelf, &removedIterator)

        handleNotification(instance: opaqueSelf, removedIterator)

        // Add the notification to the main run loop to receive future updates.
        CFRunLoopAddSource(
            CFRunLoopGetMain(),
            IONotificationPortGetRunLoopSource(notificationPort).takeUnretainedValue(),
            .commonModes)
    }

    deinit {
        IOObjectRelease(addedIterator)
        IOObjectRelease(removedIterator)
        IONotificationPortDestroy(notificationPort)
    }
}

extension io_object_t {
    /// - Returns: The device's name.
    func name() -> String? {
        let buf = UnsafeMutablePointer<io_name_t>.allocate(capacity: 1)
        defer { buf.deallocate(capacity: 1) }
        return buf.withMemoryRebound(to: CChar.self, capacity: MemoryLayout<io_name_t>.size) {
            if IORegistryEntryGetName(self, $0) == KERN_SUCCESS {
                return String(cString: $0)
            }
            return nil
        }
    }
}


import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

class Example: USBWatcherDelegate {
    private var usbWatcher: USBWatcher!
    init() {
        usbWatcher = USBWatcher(delegate: self)
    }

    func deviceAdded(_ device: io_object_t) {
        print("device added: \(device.name() ?? "<unknown>")")
    }

    func deviceRemoved(_ device: io_object_t) {
        print("device removed: \(device.name() ?? "<unknown>")")
    }
}

let example = Example()

答案 1 :(得分:2)

这个答案对我有用https://stackoverflow.com/a/35788694,但它需要一些改编,比如创建一个桥接标题来导入一些特定的IOKit部分。

首先,将IOKit.framework添加到您的项目中(单击“链接的框架和库”中的“+”)。

然后创建一个新的空“.m”文件,无论其名称如何。然后Xcode将询问它是否应该制作“桥接头”。说是。

忽略“.m”文件。在Xcode刚刚创建的新“YOURAPPNAME-Bridging-Header.h”文件中,添加以下行:

#include <IOKit/IOKitLib.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/hid/IOHIDKeys.h>

现在您可以使用链接答案中的代码。这是一个简化版本:

class USBDetector {
    class func monitorUSBEvent() {
        var portIterator: io_iterator_t = 0
        let matchingDict = IOServiceMatching(kIOUSBDeviceClassName)
        let gNotifyPort: IONotificationPortRef = IONotificationPortCreate(kIOMasterPortDefault)
        let runLoopSource: Unmanaged<CFRunLoopSource>! = IONotificationPortGetRunLoopSource(gNotifyPort)
        let gRunLoop: CFRunLoop! = CFRunLoopGetCurrent()
        CFRunLoopAddSource(gRunLoop, runLoopSource.takeRetainedValue(), kCFRunLoopDefaultMode)
        let observer = UnsafeMutablePointer<Void>(unsafeAddressOf(self))
        _ = IOServiceAddMatchingNotification(gNotifyPort,
                                              kIOMatchedNotification,
                                              matchingDict,
                                              deviceAdded,
                                              observer,
                                              &portIterator)
        deviceAdded(nil, iterator: portIterator)
        _ = IOServiceAddMatchingNotification(gNotifyPort,
                                              kIOTerminatedNotification,
                                              matchingDict,
                                              deviceRemoved,
                                              observer,
                                              &portIterator)
        deviceRemoved(nil, iterator: portIterator)
    }
}

func deviceAdded(refCon: UnsafeMutablePointer<Void>, iterator: io_iterator_t) {
    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("Active device: \(deviceName!)")
        IOObjectRelease(usbDevice)
    }
}

func deviceRemoved(refCon: UnsafeMutablePointer<Void>, iterator: io_iterator_t) {
    // ...
}

注意:deviceAddeddeviceRemoved需要是函数(而不是方法)。

要使用此代码,只需启动观察者:

USBDetector.monitorUSBEvent()

这将列出当前插入的设备,并在每个新的USB设备插件/拔出事件中,它将打印设备名称。