import Foundation
// MARK: - Extension
// [How to read a single character from a string](https://www.hackingwithswift.com/example-code/strings/how-to-read-a-single-character-from-a-string)
extension String {
subscript(i: Int) -> String {
return String(self[index(startIndex, offsetBy: i)])
}
}
// MARK: - MACAddress
struct MACAddress {
let rawValue: [UInt8]
/// 16進数表記のMACアドレス
/// e.g. bc:d0:74:2e:c2:7f
var hexadecimalString: String {
return rawValue
.map { String(format:"%02x", $0) }
.joined(separator: ":")
}
/// プライベートMACアドレスかどうか
/// [グローバル・プライベート(ローカル)MACアドレスの見分け方](https://qiita.com/IKEH/items/d2c47b9d336a5500bb63)
var isPrivate: Bool {
return ["2", "6", "A", "E"].contains(hexadecimalString[1])
}
}
// MARK: - IOServiceManager
struct IOServiceManager {
}
// MARK: Define Type
private extension IOServiceManager {
// typealiasだと型の制約がつけられなかったため新しく型を定義する
struct IOServiceIterator {
let rawValue: io_iterator_t
}
struct IOService {
let rawValue: io_iterator_t
}
}
// MARK: General Methods
private extension IOServiceManager {
static func findMatchingServices(
matchingKey: String,
propertyMatchDict: Dictionary<String, Any>? = nil
) -> IOServiceIterator? {
// IOEthernetInterfaceにマッチする辞書を作成します。
guard let matchingDictUM = IOServiceMatching(matchingKey) else {
return nil
}
let matchingDict = matchingDictUM as NSMutableDictionary
if let propertyMatchDict {
matchingDict["IOPropertyMatch"] = propertyMatchDict
}
// マッチするサービスを格納するための変数。
var matchingServices = io_iterator_t.zero
// マッチングサービスの取得に失敗した場合、nilを返します。
if #available(macOS 12.0, *) {
if IOServiceGetMatchingServices(kIOMainPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS {
return nil
}
} else {
if IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS {
return nil
}
}
// 成功した場合、マッチしたサービスのイテレータを返します。
return IOServiceIterator(rawValue: matchingServices)
}
static func findMatchingService(
matchingKey: String,
propertyMatchDict: Dictionary<String, Any>? = nil
) -> IOService? {
guard let matchingDictUM = IOServiceMatching(matchingKey) else {
return nil
}
let matchingDict = matchingDictUM as NSMutableDictionary
if let propertyMatchDict {
matchingDict["IOPropertyMatch"] = propertyMatchDict
}
if #available(macOS 12.0, *) {
return IOService(rawValue: IOServiceGetMatchingService(kIOMainPortDefault, matchingDict))
} else {
return IOService(rawValue: IOServiceGetMatchingService(kIOMasterPortDefault, matchingDict))
}
}
static func getPropertyValue<T: CFTypeRef>(from service: IOService, propertyKey: String) -> T? {
if let dataUM = IORegistryEntryCreateCFProperty(
service.rawValue,
propertyKey as CFString,
kCFAllocatorDefault,
0 // no options
) {
return dataUM.takeRetainedValue() as? T
}
return nil
}
static func getPropertyValues<T: CFTypeRef>(from intfIterator: IOServiceIterator, propertyKey: String) -> [T] {
var values: [T] = []
// イテレータからインターフェースサービスを取得します。
var intfService = IOIteratorNext(intfIterator.rawValue)
while intfService != 0 {
// コントローラーサービスを格納する変数。
var controllerService: io_object_t = 0
// 親エントリーを取得し、MACアドレスを含むコントローラーサービスを取得します。
if IORegistryEntryGetParentEntry(intfService, kIOServicePlane, &controllerService) == KERN_SUCCESS {
// コントローラーサービスからIOMACAddressプロパティを取得します。
if let dataUM = IORegistryEntryCreateCFProperty(
controllerService,
propertyKey as CFString,
kCFAllocatorDefault,
0 // no options
) {
guard let value = dataUM.takeRetainedValue() as? T else {
continue
}
values.append(value)
}
// コントローラーサービスの解放。
IOObjectRelease(controllerService)
}
// インターフェースサービスの解放。
IOObjectRelease(intfService)
// 次のインターフェースサービスを取得。
intfService = IOIteratorNext(intfIterator.rawValue)
}
return values
}
}
// MARK: Getting Info Methods
extension IOServiceManager {
/// MACアドレスの一覧を取得
static func getMacAddresses() -> [MACAddress] {
// イーサネットインターフェースのイテレータを取得します。
guard let serviceIterator = IOServiceManager.findMatchingServices(matchingKey: "IOEthernetInterface") else {
return []
}
let macAddressValues: [CFData] = IOServiceManager.getPropertyValues(from: serviceIterator, propertyKey: "IOMACAddress")
let macAddresses = macAddressValues
.map { $0 as Data }
.map { data in
// MACアドレスのための6バイトの配列を初期化し、データをコピーします。
var macAddress = [UInt8](repeating: 0, count: 6)
data.copyBytes(to: &macAddress, count: macAddress.count)
return MACAddress(rawValue: macAddress)
}
// イテレータの解放はServiceを呼び出し元で行う必要がある
// https://developer.apple.com/documentation/iokit/1514494-ioservicegetmatchingservices
IOObjectRelease(serviceIterator.rawValue)
return macAddresses
}
// MACアドレス(IOPrimaryInterface)の一覧を取得
static func getMacAddressesOfPrimaryInterface() -> [MACAddress] {
guard let serviceIterator = IOServiceManager.findMatchingServices(
matchingKey: "IOEthernetInterface",
// IOPrimaryInterfaceがtrueのインターフェースのみをマッチさせるためのフィルタを設定します。
propertyMatchDict: ["IOPrimaryInterface" : true]
) else {
return []
}
let macAddressValues: [CFData] = IOServiceManager.getPropertyValues(from: serviceIterator, propertyKey: "IOMACAddress")
let macAddresses = macAddressValues
.map { $0 as Data }
.map { data in
var macAddress = [UInt8](repeating: 0, count: 6)
data.copyBytes(to: &macAddress, count: macAddress.count)
return MACAddress(rawValue: macAddress)
}
IOObjectRelease(serviceIterator.rawValue)
return macAddresses
}
static func getPlatformSerialNumber() -> String? {
var serialNumber: String?
if
let service = IOServiceManager.findMatchingService(matchingKey: "IOPlatformExpertDevice"),
let _serialNumber = (IOServiceManager.getPropertyValue(from: service, propertyKey: "IOPlatformUUID") as CFString?) as? String
{
serialNumber = _serialNumber
IOObjectRelease(service.rawValue)
}
return serialNumber
}
static func getTemperature() -> Double {
var temperature = Double.zero
if
let service = IOServiceManager.findMatchingService(matchingKey: "AppleSmartBattery"),
let _temperature = (IOServiceManager.getPropertyValue(from: service, propertyKey: "Temperature") as CFNumber?) as? Double
{
temperature = _temperature
IOObjectRelease(service.rawValue)
}
// 温度値の補正
if temperature == 0 {
return 0
}
return temperature / 100
}
}