▲点击上方“
CocoaChina
”关注即可免费学习iOS开发
绝大多数的手机应用在某一时刻需要通过网络向后台主机或服务器请求数据或者进行数据更新。然而,网络连接并不总是一直处于可用状态,随时都有可能出现断开连接导致不可用的情况。为了解此问题,我们可以通过使用
SCNetworkReachability API
接口来获取系统当前的网络状态和检测应用是否可以连接到后台服务器。
SCNetworkReachability 接口属于 Core Foundation框架的一部分,并用C语言来实现的。对于某些开发人员来说不能直接地使用Swift语言来调用SCNetworkReachability 接口。
苹果提供了一些示例代码封装了一个叫做Reachbility的一个类,它是一个Objective-C封装在scnetworkreachability API。
Reachbility类对网络连接进行了友好的封装,可以通过便捷的方法来检查是否已经连接或者断开了网络。当网络状态改变的时候你也可以通过NSNotification把你的对象注入到NSNotifiactionCenter 来进行及时通知。
你可以在
苹果提供的示例项目
中找到Reachbility类。
如果你对Reachability这个类比较熟悉的话,你只需要使用最新版本更新代码(2015/11/11日发布的4.2版本),4.2版本修复了几个可能导致内存泄漏的BUG。
苹果公司已经提供了Objective-C版本的示例代码,但是很多开发者有对Swift版本的诉求,因此我们将在这篇文章中讲述Swift中的NetWork Reachability的API。我们将会一步步的讲解它的工作原理并用Swift 3.0实现我们自己的Reachability类。
SCNetworkReachability API说明
SCNetworkReachability API 提供一个同步的方法来判断网络是否连接状态。该同步方法允许我们通过调用SCNetworkReachabilityGetFlags函数来获取当前的网络状态。该函数的第二个参数是个指向内存的指针,来作为网络状连接状态的标识参数,此参数还包含一些额外的信息,例如网络建立连接时是否属于自动建立连接还是属于用户干预导致的。
SCNetworkReachability API 除了提供同步方法之外还提供了异步方法。为了实现这个方法,我们必须循环调用SCNetworkReachability 对象,当远程服务器连接状态改变时我们都要提供一个回调函数进行广播通知。
使用Swift实现
启动Xcode 8 并新建一个的 Swift Single View Application项目,项目命名为ReachabilityExample,在项目中添加一个新的swift文件,命名为
Reachability.swift ,添加如import声明:
import SystemConfiguration
我们想在网络出现以下三种情况进行变更时通知我们的app(改:我们想在网络出现以下三种情况的时候通知我们的APP)。
1. 当app失去连接时(改:当APP没有连接网络时)
2. 当app通过 wifi连接时
3. 当app通过 WWAN 连接时
我们将会发送一个包含网络连接状态的通知,下面我们将会为这个通知命名,并定义三种可能的网络状态:
let ReachabilityDidChangeNotificationName = "ReachabilityDidChangeNotification"
enum ReachabilityStatus {
case notReachable
case reachableViaWiFi
case reachableViaWWAN
}
在这个类中添加一个属性来保存SCNetworkReachability对象:
private var networkReachability: SCNetworkReachability?
为了监控目前服务器是否可以连接,我们创建一个初始化方法,把域名为作参数传入,并通过SCNetworkReachabilityCreateWithName 函数初始化
SCNetworkReachability对象 。如果SCNetworkReachability初始化失败则返回nil,所以我们创建一个可失败初始化方法(failable initializer):
init?(hostName: String) {
networkReachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, (hostName as NSString).UTF8String)
super.init()
if networkReachability == nil {
return nil
}
}
为了创建一个根据ip网络地址的reachability对象,我们需要实现另外一个初始化方法。这种情况我们将使用
SCNetworkReachabilityCreateWithAddress 函数。由于这个函数需要一个指向网络地址的指针,所以我们称它为withUnsafePointer函数。这种情况下,正如我们前面讲到的那样,函数的返回值可能是nil,所以要使init方法可以失败。
init?(hostAddress: sockaddr_in) {
var address = hostAddress
guard let defaultRouteReachability = withUnsafePointer(to: &address, {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, $0)
}
}) else {
return nil
}
networkReachability = defaultRouteReachability
super.init()
if networkReachability == nil {
return nil
}
}
为了方便起见,我们创建两个类方法。第一个方法建一个 reachability的实例来控制网络连接。第二个方法用来检测当我们是否连接的是本地wifi。两个方法都必须使用网络地址来使用构造方法。在本地WIFI的情况下,地址169.254.0.0被定义为IN_LINKLOCALNETNUM。
static func networkReachabilityForInternetConnection() -> Reachability? {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
return Reachability(hostAddress: zeroAddress)
}
static func networkReachabilityForLocalWiFi() -> Reachability? {
var localWifiAddress = sockaddr_in()
localWifiAddress.sin_len = UInt8(MemoryLayout.size(ofValue: localWifiAddress))
localWifiAddress.sin_family = sa_family_t(AF_INET)
// IN_LINKLOCALNETNUM is defined inas 169.254.0.0 (0xA9FE0000).
localWifiAddress.sin_addr.s_addr = 0xA9FE0000
return Reachability(hostAddress: localWifiAddress)
}
现在我们需要定定义一个开启通知和一个关闭通知方法,并定义一个属性来标识通知状态当前处于开启状态还是关闭状态:
private var notifying: Bool = false
开启通知之前,先检查通知是否为开启状态。然后获取SCNetworkReachabilityContext容器,并为context 对象的info属性赋值为self。之后设置回调函数,传递context(当回调函数调用时,info参数包含的对self的引用指针将会作为第三个参数传递给block数据).如果设置回调函数成功,我们就能在run loop中管理network reachability的引用。
func startNotifier() -> Bool {
guard notifying == false else {
return false
}
var context = SCNetworkReachabilityContext()
context.info = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
guard let reachability = networkReachability, SCNetworkReachabilitySetCallback(reachability, { (target: SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) in
if let currentInfo = info {
let infoObject = Unmanaged.fromOpaque(currentInfo).takeUnretainedValue()
if infoObject is Reachability {
let networkReachability = infoObject as! Reachability
NotificationCenter.default.post(name: Notification.Name(rawValue: ReachabilityDidChangeNotificationName), object: networkReachability)