3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

IOKitでMACアドレスなどMacの情報を取得

Last updated at Posted at 2024-09-19

概要

  • IOKitを使ってMacのMACアドレスや機体のシリアル、バッテリー温度を取得してみる
  • また使いやすいようにいくつか一般化をおこなってみた
  • コード補足などはコメントを参照

参考

実行例

// MARK: - Entry Point

print("### MAC Addresses ###")
let macAddresses = IOServiceManager.getMacAddresses()
for macAddress in macAddresses {
    print("\(macAddress.hexadecimalString) / \(macAddress.isPrivate ? "private" : "public")")
}
print("")

print("### MAC Addresses (Primary Interface) ###")
let macAddressesOfPrimaryInterface = IOServiceManager.getMacAddressesOfPrimaryInterface()
for macAddress in macAddressesOfPrimaryInterface {
    print("\(macAddress.hexadecimalString) / \(macAddress.isPrivate ? "private" : "public")")
}
print("")


print("### Serial Number ###")
if let value = IOServiceManager.getPlatformSerialNumber() {
    print(value)
}
print("")

print("### Battery Temperature ###")
print(IOServiceManager.getTemperature())
print("")
  • 出力例は以下の通り
### MAC Addresses ###
XX:XX:XX:XX:XX:48 / private
XX:XX:XX:XX:XX:f0 / public
...
XX:XX:XX:XX:XX:1f / public

### MAC Addresses (Primary Interface) ###
XX:XX:XX:XX:XX:1f / public

### Serial Number ###
XXXXXXXA-XXXX-XXXX-XXXX-XXXXXXXXXXXX

### Battery Temperature ###
30.09

IOKit部分のコード

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?