如何获取 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 那就又是下一话了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

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