前言
手机网络连接状态的检测对于 iOS App 开发来说是一个非常基础的需求,在前一篇文章 苹果示例源码阅读:Reachability 我们介绍了如何通过 SCNetworkReachability
提供的一系列 C 函数 API 进行网络连接状态变化的监听。但事实上,此方案能获取的只是设备的本地连接状态,有时它很难为我们检测真正的网络连接状态,如以下场景:
- 现在很多的公共场所的 WiFi,需要网页登录授权,授权之前无法上网,但本地连接已经建立;
- 存在了本地网络连接,但信号很差,实际无法连接到服务器;
- iOS 连接的路由设备本身没有连接外网等。
Ping
ping
是 Windows、Unix 、Linux 和 macOS 等系统下一个常用的命令,利用 ping
命令可以用来测试数据包 (ICMP) 能否通过 IP 协议到达特定主机,并收到主机的应答,以检查网络是否连通和网络连接速度,帮助我们分析和判定网络故障。
幸运的是,苹果为我们提供了示例源码:SimplePing,示范了在 iOS 或者 Mac 上如何用 Objective-C / Swift 实现 ping
操作,因此我们也可以通过 ping
来检查手机网络的真实连接状态。事实上,Github 上著名的第三方开源库 RealReachability 也是这么做的。
SimplePing 源码阅读
对于 SimplePing
源码的阅读,我们将分为两部分来介绍。第一部分将结合 SimplePing.h
头文件里声明的方法,介绍如何使用 SimplePing
类封装的方法进行 ping
操作,第二部分(下一篇)将详细介绍 SimplePing.m
里各方法的具体实现细节。
类结构
通过 SimplePing.h
头文件中的声明,我们整理 SimplePing
的类结构如下图所示:

下面我们一一介绍 SimplePing
类的各个属性、方法以及 delegate 回调方法的含义及作用。
初始化方法
1 | - (instancetype)init NS_UNAVAILABLE; |
SimplePing
中,禁用了 init
方法,只提供 initWithHostName:
一个方法,它可以初始化一个用于 ping 指定的主机实例对象。其中 hostName
参数可以是主机的 DNS 域名,或者是 IPv4、IPv6 地址的字符串形式。
属性
1 | @property (nonatomic, copy, readonly) NSString * hostName; |
hostName
:只读,保存由初始化方法initWithHostName:
传入的ping
操作要连接的主机域名或 IP 地址。
1 | @property (nonatomic, assign, readwrite) SimplePingAddressStyle addressStyle; |
addressStyle
:主机的 IP 地址类型,如IPv4
或IPv6
等,其中SimplePingAddressStyle
枚举类型的定义如下:
1 | typedef NS_ENUM(NSInteger, SimplePingAddressStyle) { |
1 | @property (nonatomic, copy, readonly, nullable) NSData * hostAddress; |
hostAddress
:只读,在start
方法调用之后,根据hostName
得到的要 ping 的主机的 IP 地址,它是struct sockaddr
形式的 NSData 数据。当 SimplePing 实例处于stopped
状态,或者实例调用了start
方法,但在simplePing:didStartWithAddress:
方法被调用之前,hostAddress
的值都是nil
。
1 | @property (nonatomic, assign, readonly) sa_family_t hostAddressFamily; |
hostAddressFamily
:只读,hostAddress
的地址族,如果hostAddress
为 nil,则其值为:AF_UNSPEC
。
1 | @property (nonatomic, assign, readonly) uint16_t identifier; |
identifier
:只读,当创建一个SimplePing
实例对象时,会自动生成一个的随机的标识符,用来唯一标识当前 ping 对象。
1 | @property (nonatomic, assign, readonly) uint16_t nextSequenceNumber; |
nextSequenceNumber
:只读,ping 每发送一次数据包都会有一个对应的序列号(sequence number
),此值为下一次 ping 操作要发送数据时的序列号,从 0 开始递增,当 ping 成功发送一次数据到主机并收到应答时,该值 +1。而对于本次 ping 的sequence number
在成功发送数据(request)和成功接收到响应(response)的 delegate 回调方法里都会以方法参数返回,以便进行 ping 操作耗时的计算等等。
1 | @property (nonatomic, weak, readwrite, nullable) id<SimplePingDelegate> delegate; |
delegate
:当前对象的回调,delegate 中的回调方法将在对象调用start
方法所在的线程对应的run loop
中以默认的run loop model
执行。
实例方法
1 | - (void)start; |
start 方法
:开始一个ping
操作,在调用此方法前,必须给SimplePing
实例对象的delegete
以及其他参数赋值。当start
方法成功执行时,会回调 delegate 中的simplePing:didStartWithAddress:
方法,在该回调方法里,就可以通过sendPingWithData:
开始发送ICMP
数据包,并等待接受主机应答的数据包。另外需要注意的是,当一个实例已经started
,又一次调用此start
方法会出错。
1 | - (void)sendPingWithData:(nullable NSData *)data; |
sendPingWithData: 方法
:向主机发送特定格式的 ICMP 数据包,调用此方法前必须保证实例已经started
并且要等待simplePing:didStartWithAddress:
回调执行才能开始发送数据。参数data
为要向主机发送的 ICMP 数据包,可以为nil
,默认会发一个标准的 64 byte 数据包。
1 | - (void)stop; |
stop 方法
:当结束要ping
操作时,调用此方法。与start
方法不同的是,当一个实例已经stopped
,再次调用此方法也没事。
delegate 回调方法
start
方法执行结果的回调:
1 | // start 方法成功执行,可在此开始发送数据,其中 address 为主机的 IP 地址; |
sendPingWithData:
方法执行结果的回调,每发送一次数据,都会同步地回调以下两个方法其中一个(除非你在发送途中调用了stop
方法):
1 | // 成功发送 ICMP 数据包到指定主机,在此传回已发送的数据包以及本次 ping 对应的序列号; |
- 接收到主机返回应答数据的回调:
1 | // 成功接收到主机回传的与之前发送相匹配的 ICMP 数据包; |
注:以上回调方法中的 packet
数据包只包含了 ICMP
header 和 sendPingWithData:
中传入的数据,但不包含任何 IP 层的 header。
使用流程
根据苹果提供的 Demo,我们梳理了一下使用 SimplePing
类进行 ping
操作的流程如下图所示:

根据上图,我们写了一个简单的使用示例,详见下面代码以及注释,不再赘述。
1 |
|
总结
本篇文章只介绍了如何使用苹果提供的示例源码 SimplePing
类初始化一个实例在 iOS 设备上进行 ping 操作,以进行判断网络真实连接状态,在下一篇文章《苹果实例源码阅读:SimplePing(2)》,我们将介绍 SimplePing
类的各个方法的具体内部实现。