Swift：macでUSBデバイスの接続/排出を検知する．

# 背景
シリアル通信向けのライブラリを作っている際にUSBデバイスの接続/排出の検出が必要となった．

# 下準備
プロジェクトの`Capabilities`の`App Sandbox`の`USB`にチェックを入れる

# ソース
```swift:USBDetector.swift
import Foundation
import IOKit
import IOKit.usb

public protocol USBDetectorDelegate: NSObjectProtocol {
    func deviceAdded(_ device: io_object_t)
    func deviceRemoved(_ device: io_object_t)
}

public class USBDetector {
    public weak var delegate: USBDetectorDelegate?
    private let notificationPort = IONotificationPortCreate(kIOMasterPortDefault)
    private var addedIterator: io_iterator_t = 0
    private var removedIterator: io_iterator_t = 0
    
    public func start() {
        let matchingDict = IOServiceMatching(kIOUSBDeviceClassName)
        let opaqueSelf = Unmanaged.passUnretained(self).toOpaque()
        
        let runLoop = IONotificationPortGetRunLoopSource(notificationPort)!.takeRetainedValue()
        CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoop, CFRunLoopMode.defaultMode)
        
        let addedCallback: IOServiceMatchingCallback = { (pointer, iterator) in
            let detector = Unmanaged<USBDetector>.fromOpaque(pointer!).takeUnretainedValue()
            detector.delegate?.deviceAdded(iterator)
            while case let device = IOIteratorNext(iterator), device != IO_OBJECT_NULL {
                IOObjectRelease(device)
            }
        }
        
        IOServiceAddMatchingNotification(notificationPort,
                                         kIOPublishNotification,
                                         matchingDict,
                                         addedCallback,
                                         opaqueSelf,
                                         &addedIterator)
        
        while case let device = IOIteratorNext(addedIterator), device != IO_OBJECT_NULL {
            IOObjectRelease(device)
        }
        
        let removedCallback: IOServiceMatchingCallback = { (pointer, iterator) in
            let watcher = Unmanaged<USBDetector>.fromOpaque(pointer!).takeUnretainedValue()
            watcher.delegate?.deviceRemoved(iterator)
            while case let device = IOIteratorNext(iterator), device != IO_OBJECT_NULL {
                IOObjectRelease(device)
            }
        }
        
        IOServiceAddMatchingNotification(notificationPort,
                                         kIOTerminatedNotification,
                                         matchingDict,
                                         removedCallback,
                                         opaqueSelf,
                                         &removedIterator)
        
        while case let device = IOIteratorNext(removedIterator), device != IO_OBJECT_NULL {
            IOObjectRelease(device)
        }
    }
    
    deinit {
        Swift.print("deinit")
        IOObjectRelease(addedIterator)
        IOObjectRelease(removedIterator)
        IONotificationPortDestroy(notificationPort)
    }
    
}
```

```swift:ViewController.swift
import Cocoa

class ViewController: NSViewController, USBDetectorDelegate {
    
    let detector = USBDetector()

    override func viewDidLoad() {
        super.viewDidLoad()
        detector.delegate = self
        detector.start()
    }

    override var representedObject: Any? {
        didSet {
        }
    }
    
    func deviceAdded(_ device: io_object_t) {
        Swift.print("Added")
    }
    
    func deviceRemoved(_ device: io_object_t) {
        Swift.print("Removed")
    }
}
```

# 参考
StackOverflow [USB Connection Delegate on Swift](https://stackoverflow.com/questions/39003986/usb-connection-delegate-on-swift)