Is there a delegate in Swift that would let my class know when new devices are plugged in via the computer's USB? I would like to know when a new device becomes available to my program.
-
Look for `NSWorkspace` Volume Mounting Notification User Info Keys. [link](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSWorkspace_Class/#//apple_ref/doc/constant_group/Volume_Mounting_Notification_User_Info_Keys) – Khundragpan Aug 17 '16 at 18:42
-
Actually, your code would be the delegate, whose methods would be called at various points in time. I'm not sure what the owner of a delegate is called, though – Alexander Aug 17 '16 at 18:46
-
Possible duplicate of http://stackoverflow.com/questions/34628464/how-to-implement-ioservicematchingcallback-in-swift/39662693 – jtbandes Dec 22 '16 at 17:47
-
I have created [USBDeviceSwift](https://github.com/Arti3DPlayer/USBDeviceSwift) library for convenient work with `IOKit.usb` and `IOKit.hid` – Arti Jun 29 '17 at 10:33
-
https://github.com/Jinjinov/Usb.Events/blob/master/Usb.Events/UsbEventWatcher.Mac.c – Jinjinov Apr 29 '20 at 07:29
2 Answers
Eric Aya's answer is already quite good, but here's a Swift 3 adaptation. I wrapped most of the ugly stuff in a USBWatcher
class; set yourself as the delegate of this object to receive notifications.
You can copy/paste the following into a playground to see it work — the example just logs a message to the console when devices are connected/disconnected.
It's unfortunate that the IOKit APIs haven't gotten the same Swift-ifying treatment that some other C APIs have been (e.g. CoreGraphics). io_name_t
is a clunky tuple instead of a proper struct, the way C structs are usually imported to Swift; io_object_t
isn't a real reference type, so it can't take advantage of ARC. Perhaps in the future this will change — if you'd like to see a better Swift API, you should 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()

- 115,675
- 35
- 233
- 266
-
Awesome answer, thanks a lot! I'm not sure I really understand the memory management here, for example `let watcher = Unmanaged
.fromOpaque(instance!).takeUnretainedValue()` is a bit mysterious to me. // Anyway, that's really great, and I specifically appreciate having a Playground example. I will study this kind of memory management using this answer as an example. Thanks again! :) – Eric Aya Dec 22 '16 at 10:54 -
2@EricAya: Opaque pointers are a way to "tunnel" a reference to an instance of a class through C functions. It is often used in connection with functions/closures which are used as C callbacks and cannot capture context. Compare (and I apologize for the self-advertisement) http://stackoverflow.com/questions/33294620/how-to-cast-self-to-unsafemutablepointervoid-type-in-swift, and http://stackoverflow.com/questions/33260808/swift-proper-use-of-cfnotificationcenteraddobserver-w-callback for another example which demonstates the usage. – Martin R Dec 22 '16 at 15:37
-
@MartinR Those `bridge()` helpers are pretty cool! I hadn't thought of doing that. Overloading makes that quite nice. – jtbandes Dec 22 '16 at 16:47
-
3I have created [USBDeviceSwift](https://github.com/Arti3DPlayer/USBDeviceSwift) library for convenient work with `IOKit.usb` and `IOKit.hid` – Arti Jun 29 '17 at 10:32
-
This is the only code that worked for me, Swift 5, Xcode 11.5. Thanks@ jtbandes! – Dohab Jun 02 '20 at 17:13
-
-
@jtbandes does `Example()` always have to go at the beginning of `class AppDelegate` ? The methods I need to fire in `deviceAdded()` are in another ViewController and this is where I'm stuck. – Mikeumus Apr 03 '21 at 18:51
-
1It shouldn't matter. The notification listener will be tied to the lifetime of the USBWatcher object, since it automatically unregisters itself in `deinit`. – jtbandes Apr 03 '21 at 19:25
This answer worked for me https://stackoverflow.com/a/35788694 but it needed some adaptation, like creating a bridging header to import some specific IOKit parts.
First, add IOKit.framework to your project (click "+" in "Linked Frameworks and Libraries").
Then create a new empty ".m" file, whatever its name. Xcode will then ask if it should make a "bridging header". Say YES.
Ignore the ".m" file. In the new "YOURAPPNAME-Bridging-Header.h" file that Xcode just created, add the following lines:
#include <IOKit/IOKitLib.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/hid/IOHIDKeys.h>
Now you can use the code in the linked answer. Here's a simplified version:
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) {
// ...
}
Note: deviceAdded
and deviceRemoved
need to be functions (not methods).
To use this code, just launch the observer:
USBDetector.monitorUSBEvent()
This will list the currently plugged devices, and on every new USB device plug/unplug event it will print the device name.
-
1This is written for Swift 2.2? I'm trying to get this working in Swift 3, but to no avail :/ Any ideas on the changes that would need to be made? – simonthumper Sep 21 '16 at 22:45
-
I've set a bounty on this question - let's hope we'll get an interesting Swift 3 answer. – Eric Aya Dec 21 '16 at 20:32
-
@EricAya: Did you notice that the Q&A that you reference has an answer for Swift 3 as well? – Martin R Dec 22 '16 at 15:45
-
@MartinR No, I missed that, thanks. No problem though, I'm super happy with jtbandes' answer (awesome code) and will use it to study interactions with C and USB in Swift 3. I think I would have put a bounty here anyway even if I had seen this earlier, I was curious to see how someone would write a good Swift 3 version for this specific question. – Eric Aya Dec 22 '16 at 15:56
-
I have created [USBDeviceSwift](https://github.com/Arti3DPlayer/USBDeviceSwift) library for convenient work with `IOKit.usb` and `IOKit.hid` – Arti Jun 29 '17 at 10:32