如何获取 iOS 的网络流量数据

最近在做直播相关的一些内容,其中一个需求就是在直播时,能够实时地显示当前的网络状况,包括上下行的速度与累计使用的流量。遂做了一些相关的研究,发现所有的检索结果都指向了 ifaddrs.h 。依照文件头部的版权申明, ifaddrs.h 来自 FreeBSD 的项目,版权属于 Berkeley Software Design, Inc.

FreeBSD 是一种自由的类 Unix 操作系统,它起源于 AT&T Unix,是经过 BSD 、 386BSD 和 4.4BSD 发展而来的类 Unix 的一个重要分支。
—— 摘自维基百科

当然追根溯源并不是今天的重点,而且网上一堆 OC 的现成例子,抄了就能用。不过作为一名 Swift 老司机,怎么用 Swift 做实现才是一名好司机的关键。下面我们发车!

如何自己将 C.h 封装成一个 Module

首先,基于扁平与模块化的思想,直接将 ifaddrs.h 放到 Objective-C bridging header 做桥接肯定是不妥的,而且如果要将其加到一个 framework 中,这样也是不允许的。

Swift Complier - Search Path - Import Path
Swift Complier - Search Path - Import Path

在你的项目中,定位到你 PROJECT 的 Build Setting,过滤器中可以输入一个 import,然后找到 Swift Complier - Search Path 大项中的 Import Path。这里你可以按照平台划分,来加入一些 modulemap 的检索路径。你实际输入的时候可能是这样的 $(SRCROOT)/SystemModule/ifaddrs/iphoneos
而 module.modulemap 文件的内容,此处以 iphoneos 平台为例,至少需要同时支持 iphoneos 与 iphonesimulator,不同平台的具体路径可以依葫芦画瓢,检索一下即可:

module ifaddrs {
    header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/ifaddrs.h"
    export *
}

然后,你就可以在你的项目中直接 import ifaddrs 了。

getifaddrs(_:) -> Int32 函数的一些使用说明与 Swift 指针的初见

该函数可以获取所有系统的网络接口的信息,不仅仅是全局的联网数据,同时 IP 地址也可以从这里获取。
此时你已经可以看到 getifaddrs(_:) -> Int32 在 Swift 下面的具体方法签名了

public func getifaddrs(_: UnsafeMutablePointer<UnsafeMutablePointer<ifaddrs>?>!) -> Int32

少年,回想起当年第一次面对 C 语言的指针、指针的指针的恐惧了吗?UnsafeMutablePointer 即为 Swift 下可变指针的具体类型,至少比星号看起来舒服多了。此处需要构造一个指针的指针,实际类型为 ifaddrs

var addrsPointer: UnsafeMutablePointer<ifaddrs>? = nil
if getifaddrs(&addrsPointer) == 0 {
    // Do something
}

getifaddrs(_:) -> Int32 函数会创建一个链表,链表上的每个节点都是一个 ifaddrs 结构体,并返回链表第一个元素的指针。成功返回 0 , 失败返回 -1 。并在最后使用 freeifaddrs(_:) 函数来释放申请的内存空间。

var pointer = addrsPointer
while pointer != nil {
    // Do something
    pointer = pointer?.pointee.ifa_next
}
freeifaddrs(addrsPointer)

注意:在 Swift3 中,指针取其实际的对象的方法已从 memory 变成了 pointee ,其具体的签名为:

public var pointee: Pointee { get nonmutating set }

通过判断每一个 ifaddrs 结构体的 ifa_addr 属性的 sa_family 字段是否为 AF_LINK 来过滤进行流量监控内容

if let addrs = pointer?.pointee {
    let name = String(cString: addrs.ifa_name)
    if addrs.ifa_addr.pointee.sa_family == UInt8(AF_LINK) {
        // Do something
    }
}

最后根据其 name 来判断流量属于 Wi-Fi 还是 WWAN。
这里还有一个小坑,ifaddrs 结构体的 ifa_data 字段的类型是 UnsafeMutableRawPointer! 。而目标需要使用的类型,或者说它实际的类型是 if_data 。如果你直接使用 if let 编译器会告诉你这是不相关的类型,无法成功转换。此处需要使用 Swift 标准库中的 unsafeBitCast 的方法,其具体签名为:

public func unsafeBitCast<T, U>(_ x: T, to: U.Type) -> U

引用 王巍 @onevcat 的原话:

unsafeBitCast 是非常危险的操作,它会将一个指针指向的内存强制按位转换为目标的类型。因为这种转换是在 Swift 的类型管理之外进行的,因此编译器无法确保得到的类型是否确实正确,你必须明确地知道你在做什么。
—— 原文《Swift 中的指针使用》

完整代码如下:

if getifaddrs(&addrsPointer) == 0 {
    var pointer = addrsPointer
    while pointer != nil {
        if let addrs = pointer?.pointee {
            let name = String(cString: addrs.ifa_name)
            if addrs.ifa_addr.pointee.sa_family == UInt8(AF_LINK) {
                if name.hasPrefix("en") { // Wifi
                    let networkData = unsafeBitCast(addrs.ifa_data, to: UnsafeMutablePointer<if_data>.self)
                    result.wifi.received += networkData.pointee.ifi_ibytes
                    result.wifi.sent += networkData.pointee.ifi_obytes
                } else if name.hasPrefix("pdp_ip") { // WWAN
                    let networkData = unsafeBitCast(addrs.ifa_data, to: UnsafeMutablePointer<if_data>.self)
                    result.wwan.received += networkData.pointee.ifi_ibytes
                    result.wwan.sent += networkData.pointee.ifi_obytes
                }
            }
        }
        pointer = pointer?.pointee.ifa_next
    }
    freeifaddrs(addrsPointer)
}

结语

至此,你已经拿到全局级别的网络数据,注意单位是 bytes,至于怎么转化为最终使用的 1.024 kb/s 或是 已使用 10.24 MB ,我相信已经难不倒各位老司机了。当然,如果你是一名只专注上车的乘客,不如试一下我已经做好的封装 TrafficPolice 。其实,当我第一次看到 Traffic 一词有流量的意思,我也是表示,英语已经全还给老师了,囧。当然,上车注意请刷卡(加星!)。暂时仅支持 Carthage 部署,不是我懒,的确是想为这么好的工具打一次硬广,您就试一下呗。至于如何支持 CocoaPod 那就又是下一话了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 核心能力是当前国外教育和外语教育界的一个热点问题。在2015 年的中心工作会议上我谈到,根据我国的社会文化情境,使...
    静_静_阅读 2,462评论 0 5
  • 文/韦剑 刺破最后一抹黑暗 你把万物从沉睡中拉醒 在枝繁叶茂间,在绿草上 晒干了昨晚凝聚的晶珠 你却不知 还有路灯...
    韦剑阅读 222评论 0 1
  • 前几天看到“道德绑架”这个说法,上网查了一下,所谓的“道德绑架”,是在用圣人的标准要求普通人,用美德来要求道德义务...
    胡义华阅读 2,335评论 0 1
  • 周日,与朋友约,暴走西湖一整圈。38830步,24.19公里,早9点到晚8点,结束一天的行程。脚残,已累瘫。 晚上...
    Cherish5240阅读 379评论 0 2

友情链接更多精彩内容