Как получить MAC-адрес от OSX с Swift

можно ли получить MAC-адрес с помощью Swift?

MAC-адрес является основным адресом для Wi-Fi или аэропорта.

Я пытаюсь сделать приложение OS X.

4 ответов


отказ от ответственности: это не производство-готовая. Вероятно, он будет отклонен App Store. Он также подвержен ошибкам, если вывод ifconfig изменения в будущем. Я сделал это, потому что мне не хватало навыков для перевода кода C, приведенного в ссылках. Он не заменяет полное решение Swift. Тем не менее, это работает...

Get ifconfigвыводит и анализирует его, чтобы получить MAC-адрес, связанный с интерфейсом (en0 в этом примере):

let theTask = NSTask()
let taskOutput = NSPipe()
theTask.launchPath = "/sbin/ifconfig"
theTask.standardOutput = taskOutput
theTask.standardError = taskOutput
theTask.arguments = ["en0"]

theTask.launch()
theTask.waitUntilExit()

let taskData = taskOutput.fileHandleForReading.readDataToEndOfFile()

if let stringResult = NSString(data: taskData, encoding: NSUTF8StringEncoding) {
    if stringResult != "ifconfig: interface en0 does not exist" {
        let f = stringResult.rangeOfString("ether")
        if f.location != NSNotFound {
            let sub = stringResult.substringFromIndex(f.location + f.length)
            let range = Range(start: advance(sub.startIndex, 1), end: advance(sub.startIndex, 18))
            let result = sub.substringWithRange(range)
            println(result)
        }
    }
}

пример кода Apple из https://developer.apple.com/library/mac/samplecode/GetPrimaryMACAddress/Introduction/Intro.html для получения MAC-адреса Ethernet можно быть переведенным на Swift. Я сохранил только самое важное. комментарии, дополнительные пояснения можно найти в исходном коде.

// Returns an iterator containing the primary (built-in) Ethernet interface. The caller is responsible for
// releasing the iterator after the caller is done with it.
func FindEthernetInterfaces() -> io_iterator_t? {

    let matchingDictUM = IOServiceMatching("IOEthernetInterface");
    // Note that another option here would be:
    // matchingDict = IOBSDMatching("en0");
    // but en0: isn't necessarily the primary interface, especially on systems with multiple Ethernet ports.

    if matchingDictUM == nil {
        return nil
    }
    let matchingDict = matchingDictUM.takeUnretainedValue() as NSMutableDictionary
    matchingDict["IOPropertyMatch"] = [ "IOPrimaryInterface" : true]

    var matchingServices : io_iterator_t = 0
    if IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS {
        return nil
    }

    return matchingServices
}

// Given an iterator across a set of Ethernet interfaces, return the MAC address of the last one.
// If no interfaces are found the MAC address is set to an empty string.
// In this sample the iterator should contain just the primary interface.
func GetMACAddress(intfIterator : io_iterator_t) -> [UInt8]? {

    var macAddress : [UInt8]?

    var intfService = IOIteratorNext(intfIterator)
    while intfService != 0 {

        var controllerService : io_object_t = 0
        if IORegistryEntryGetParentEntry(intfService, "IOService", &controllerService) == KERN_SUCCESS {

            let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress", kCFAllocatorDefault, 0)
            if dataUM != nil {
                let data = dataUM.takeRetainedValue() as! NSData
                macAddress = [0, 0, 0, 0, 0, 0]
                data.getBytes(&macAddress!, length: macAddress!.count)
            }
            IOObjectRelease(controllerService)
        }

        IOObjectRelease(intfService)
        intfService = IOIteratorNext(intfIterator)
    }

    return macAddress
}


if let intfIterator = FindEthernetInterfaces() {
    if let macAddress = GetMACAddress(intfIterator) {
        let macAddressAsString = ":".join(macAddress.map( { String(format:"%02x", ) } ))
        println(macAddressAsString)
    }

    IOObjectRelease(intfIterator)
}

единственная "сложная" часть-как работать с Unmanaged объекты, те есть суффикс UM в моем коде.

вместо возврата ошибки код, функции возвращают необязательный значение, которое nil если функция не удалась.


обновление для Swift 3:

func FindEthernetInterfaces() -> io_iterator_t? {

    let matchingDict = IOServiceMatching("IOEthernetInterface") as NSMutableDictionary
    matchingDict["IOPropertyMatch"] = [ "IOPrimaryInterface" : true]

    var matchingServices : io_iterator_t = 0
    if IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &matchingServices) != KERN_SUCCESS {
        return nil
    }

    return matchingServices
}

func GetMACAddress(_ intfIterator : io_iterator_t) -> [UInt8]? {

    var macAddress : [UInt8]?

    var intfService = IOIteratorNext(intfIterator)
    while intfService != 0 {

        var controllerService : io_object_t = 0
        if IORegistryEntryGetParentEntry(intfService, "IOService", &controllerService) == KERN_SUCCESS {

            let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress" as CFString, kCFAllocatorDefault, 0)
            if let data = dataUM?.takeRetainedValue() as? NSData {
                macAddress = [0, 0, 0, 0, 0, 0]
                data.getBytes(&macAddress!, length: macAddress!.count)
            }
            IOObjectRelease(controllerService)
        }

        IOObjectRelease(intfService)
        intfService = IOIteratorNext(intfIterator)
    }

    return macAddress
}

if let intfIterator = FindEthernetInterfaces() {
    if let macAddress = GetMACAddress(intfIterator) {
        let macAddressAsString = macAddress.map( { String(format:"%02x", ) } )
            .joined(separator: ":")
        print(macAddressAsString)
    }

    IOObjectRelease(intfIterator)
}

разный подход через if_msghdr

func MACAddressForBSD(bsd : String) -> String?
{
  let MAC_ADDRESS_LENGTH = 6
  let separator = ":"

  var length : size_t = 0
  var buffer : [CChar]

  let BSDIndex = Int32(if_nametoindex(bsd))
  if BSDIndex == 0 {
    println("Error: could not find index for bsd name \(bsd)")
    return nil
  }
  let bsdData = bsd.dataUsingEncoding(NSUTF8StringEncoding)!

  var managementInfoBase = [CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, BSDIndex]

  if sysctl(&managementInfoBase, 6, nil, &length, nil, 0) < 0 {
    println("Error: could not determine length of info data structure");
    return nil;
  }

  buffer = [CChar](count: length, repeatedValue: 0)

  if sysctl(&managementInfoBase, 6, &buffer, &length, nil, 0) < 0 {
    println("Error: could not read info data structure");
    return nil;
  }

  let infoData = NSData(bytes: buffer, length: length)
  var interfaceMsgStruct = if_msghdr()
  infoData.getBytes(&interfaceMsgStruct, length: sizeof(if_msghdr))
  let socketStructStart = sizeof(if_msghdr) + 1
  let socketStructData = infoData.subdataWithRange(NSMakeRange(socketStructStart, length - socketStructStart))
  let rangeOfToken = socketStructData.rangeOfData(bsdData, options: NSDataSearchOptions(0), range: NSMakeRange(0, socketStructData.length))
  let macAddressData = socketStructData.subdataWithRange(NSMakeRange(rangeOfToken.location + 3, MAC_ADDRESS_LENGTH))
  var macAddressDataBytes = [UInt8](count: MAC_ADDRESS_LENGTH, repeatedValue: 0)
  macAddressData.getBytes(&macAddressDataBytes, length: MAC_ADDRESS_LENGTH)
  let addressBytes = macAddressDataBytes.map({ String(format:"%02x", ) })

  return join(separator, addressBytes)
}


MACAddressForBSD("en0")

обновление до записи Мартина Р. Есть несколько строк, которые не будут компилироваться с Swift 2.1.

изменения:

let matchingDict = matchingDictUM.takeUnretainedValue() as NSMutableDictionary

в:

let matchingDict = matchingDictUM as NSMutableDictionary

изменения:

let macAddressAsString = ":".join(macAddress.map( { String(format:"%02x", ) } ))

в:

let macAddressAsString = macAddress.map( { String(format:"%02x", ) } ).joinWithSeparator(":")