如何使用Swift从OS X获取MAC地址

12

使用Swift获取MAC地址是否可行?

MAC地址是Wi-Fi或Airport的主要地址。

我正在尝试创建一个OS X应用程序。


嗯,我尝试查看Swift的API,看是否有提及获取MAC地址的方法。但我只发现了NSHost.currentHost().address,它返回的并不是MAC地址。我也找到了在IOS中获取MAC地址的方法,但那是使用UIDevice,与OS X应用程序无关。 - jimbob
有C代码,但我不知道Swift是否可以集成C。 - jimbob
你是指Objective-C和C吗?Objective-C和C是不同的编程语言。 - Huang Chen
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - TheDarkKnight
我有一段获取MAC地址的Swift代码片段,但它使用了NSTask,因此可能不适合App Store,并且它解析字符串以获取结果,所以它是hacky的,如果未来发生更改,则容易出现错误。如果您仍然感兴趣并了解这些限制,我会将其发布为答案。 - Eric Aya
显示剩余5条评论
6个回答

16

苹果从https://developer.apple.com/library/mac/samplecode/GetPrimaryMACAddress/Introduction/Intro.html获取以太网MAC地址的示例代码可以转换为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", $0) } ))
        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", $0) } )
            .joined(separator: ":")
        print(macAddressAsString)
    }

    IOObjectRelease(intfIterator)
}

我对一些涉及C语言的具体事项进行Swift翻译非常好奇,做得很棒;)! - Fantattitude

5

通过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 {
        print("Error: could not find index for bsd name \(bsd)")
        return nil
    }
    let bsdData = Data(bsd.utf8)
    var managementInfoBase = [CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, bsdIndex]

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

    buffer = [CChar](unsafeUninitializedCapacity: length, initializingWith: {buffer, initializedCount in
        for x in 0..<length { buffer[x] = 0 }
        initializedCount = length
    })

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

    let infoData = Data(bytes: buffer, count: length)
    let indexAfterMsghdr = MemoryLayout<if_msghdr>.stride + 1
    let rangeOfToken = infoData[indexAfterMsghdr...].range(of: bsdData)!
    let lower = rangeOfToken.upperBound
    let upper = lower + MAC_ADDRESS_LENGTH
    let macAddressData = infoData[lower..<upper]
    let addressBytes = macAddressData.map{ String(format:"%02x", $0) }
    return addressBytes.joined(separator: separator)
}

MACAddressForBSD(bsd: "en0")

5

Swift 4.2更新

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! 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, kIOServicePlane, &controllerService) == KERN_SUCCESS {

            let dataUM = IORegistryEntryCreateCFProperty(controllerService, "IOMACAddress" as CFString, kCFAllocatorDefault, 0)
            if dataUM != nil {
                let data = (dataUM!.takeRetainedValue() as! CFData) as Data
                macAddress = [0, 0, 0, 0, 0, 0]
                data.copyBytes(to: &macAddress!, count: macAddress!.count)
            }
            IOObjectRelease(controllerService)
        }

        IOObjectRelease(intfService)
        intfService = IOIteratorNext(intfIterator)
    }

    return macAddress
}


func getMacAddress() -> String? {
    var macAddressAsString : String?
    if let intfIterator = FindEthernetInterfaces() {
        if let macAddress = GetMACAddress(intfIterator) {
            macAddressAsString = macAddress.map( { String(format:"%02x", $0) } ).joined(separator: ":")
            print(macAddressAsString!)
        }

        IOObjectRelease(intfIterator)
    }
    return macAddressAsString
}

4

更新Martin R的条目。有几行代码在Swift 2.1下无法编译。

更改为:

let matchingDict = matchingDictUM.takeUnretainedValue() as NSMutableDictionary

致:

let matchingDict = matchingDictUM as NSMutableDictionary

更改:

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

致:

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

3

您也可以使用'SystemConfiguration'框架

import SystemConfiguration

func collectMACAddresses() -> [String] {
    guard let interfaces = SCNetworkInterfaceCopyAll() as? [SCNetworkInterface] else {
        return []
    }
    
    return interfaces
        .map(SCNetworkInterfaceGetHardwareAddressString)
        .compactMap { $0 as String? }
}

0

免责声明:这不是生产就绪的代码。它可能会被应用商店拒绝。如果ifconfig的输出在未来发生变化,它也可能存在错误。 我制作了这个工具,因为我缺乏翻译链接中给出的C代码的技能。它不能替代完整的Swift解决方案。话虽如此,它仍然可以使用...

获取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)
        }
    }
}

1
非常感谢!这比我之前的方法要好多了,那是运行以下Bash命令:networksetup -listallhardwareports | grep -A 2 "Hardware Port: Air" | grep "Ethernet Address" | cut -d ":" -f2- - jimbob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接