Network Reachability in Swift

This API is part of Core Foundation, so it written in C. If you are using Swift this API is not really straightforward to use for many people. Apple provides some sample code containing a class called Reachability , which is an Objective-C wrapper around the SCNetworkReachability API. This class encapsulates how to access the network, along with some convenience methods for asking if we are connected or not to the Internet. It also allows to register your objects to NSNotificationCenter to be notified through a NSNotification object when the network status changes. You can find this wrapper class in a sample code project available at:https://developer.apple.com/library/ios/samplecode/Reachability/Introduction/Intro.htmlIf you are already familiar with the Reachability class, you should only update your code with the latest version (4.2 from November 11th, 2015) that fixes a couple of bugs that could potentially leak some memory.The sample code provided by Apple is written in Objective-C, and since we get a lot of petitions from developers requesting the Swift version, we are going to address that in this post. We will write our own Reachability class in Swift 3.0 and explain how it works, step by step.The SCNetworkReachability APIThe SCNetworkReachability API provides a synchronous method to determine the reachability. The synchronous method allows us to request the current status of the reachability by calling the SCNetworkReachabilityGetFlags function. The second parameter of this function is a pointer to memory that will be filled with flags that describe the reachability status and will provide extra information such as whether a connection is required and whether some user intervention might be required when establishing a connection.In addition to the synchronous method, the SCNetworkReachability API provides an asynchronous method. To implement this approach we need to schedule the SCNetworkReachability object in a run loop. Providing a callback function we can then post a notification whenever the reachability of the remote host changes.Let’s do it in SwiftLaunch Xcode 8 and create a new Swift Single View Application project. Name it ReachabilityExample . Let's immediately add a new swift file to our project. Name it Reachability.swift . Let's add to this file the following import statement:import SystemConfiguration We want to inform our App when the network status changes in three possible scenarios.when the app is not connected,when the app is connected via Wifi,when the app is connected via WWAN.We will do so by sending a notification containing the status of the connection. So, let's define the notification name and the three possible states:let ReachabilityDidChangeNotificationName = "ReachabilityDidChangeNotification" enum ReachabilityStatus {case notReachablecase reachableViaWiFicase reachableViaWWAN} Let's now implement the Reachability class.class Reachability:NSObject { } Let's add to this class a property to store the SCNetworkReachability object:privatevar networkReachability: SCNetworkReachability? For cases where we want to monitor the reachability of the target host, we create an initializer that takes the node name of the desired host as parameter, and creates a SCNetworkReachability object with the SCNetworkReachabilityCreateWithName function. This function can return nil , if it's not able to create a valid SCNetworkReachability object. So, our initializer will be a failable initializer:init?(hostName: String) {    networkReachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, (hostNameas NSString).UTF8String)    super.init()    if networkReachability == nil {    return nil    }} We need to create an additional initializer for cases where we want to create a reachability reference to the network address. In this case we will use the SCNetworkReachabilityCreateWithAddress function. As this function expects a pointer to a network address, we will call it inside a withUnsafePointer function. Also in this case, we need to make the init failable, as there is also the possibility of returning a nil value as explained before:init?(hostAddress: sockaddr_in) {    var address = hostAddress     guardlet 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    }} For convenience, we create a couple of class methods. The first method creates a reachability instance to control if we are connected to internet. The second method allows to check if we are connected to a local WiFi. Both methods use the initializers that we have just created with a network address. In case of the local WiFi, the address will point to 169.254.0.0 as defined in for 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)} Now we need a couple of methods to start and stop notifying and a property to save a state if we are notifying changes or not:privatevar notifying: Bool = false To start notifying, we first check that we are not already doing so. Then, we get a SCNetworkReachabilityContext and we assign self to its info parameter. After that we set the callback function, passing also the context (when the callback function is called, the info parameter that contains a reference to self will be passed as pointer to a data block as third parameter). If setting the callback function is successful, we can then schedule the network reachability reference in a run loop.func startNotifier() -> Bool {     guardnotifying == false else {        return false    }     var context = SCNetworkReachabilityContext()    context.info = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())     guardlet 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 = infoObjectas! Reachability

NotificationCenter.default.post(name: Notification.Name(rawValue: ReachabilityDidChangeNotificationName), object: networkReachability)

}

}

}, &context) == true else { return false }

guardSCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode.rawValue) == true else { return false }

notifying = true

return notifying

}

To stop notifying, we just unschedule the network reachability reference from the run loop:

func stopNotifier() {

if let reachability = networkReachability, notifying == true {

SCNetworkReachabilityUnscheduleFromRunLoop(reachability, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode as! CFString)

notifying = false

}

}

We should also make sure that we stop notifying before the Reachability object is deallocated:

deinit {

stopNotifier()

}

To know the state of the network reachability, we can define a computed property that will get the current flags of the SCNetworkReachability object:

privatevar flags:SCNetworkReachabilityFlags {

var flags = SCNetworkReachabilityFlags(rawValue: 0)

if let reachability = networkReachability, withUnsafeMutablePointer(to: &flags, { SCNetworkReachabilityGetFlags(reachability, UnsafeMutablePointer($0)) }) == true {

return flags

}

else {

return []

}

}

I create now a method that is able to return the reachability status for the set of flags passed to it (the logic used to determine which flag means which type of connection is explained in the comments of the method):

var currentReachabilityStatus:ReachabilityStatus {

if flags.contains(.reachable) == false {

// The target host is not reachable.

return .notReachable

}

else if flags.contains(.isWWAN) == true {

// WWAN connections are OK if the calling application is using the CFNetwork APIs.

return .reachableViaWWAN

}

else if flags.contains(.connectionRequired) == false {

// If the target host is reachable and no connection is required then we'll assume that you're on Wi-Fi...

return .reachableViaWiFi

}

else if (flags.contains(.connectionOnDemand) == true || flags.contains(.connectionOnTraffic) == true) && flags.contains(.interventionRequired) == false {

// The connection is on-demand (or on-traffic) if the calling application is using the CFSocketStream or higher APIs and no [user] intervention is needed

return .reachableViaWiFi

}

else {

return .notReachable

}

}

If you want to know more about the different flags that are available and the meaning of each one, you can read the official documentation for the flags .

Finally, we can create a method to determine the reachability status of the current flags of our network reachability reference, and a computed property to verify if we are connected or not:

var isReachable:Bool {

switch currentReachabilityStatus {

case .notReachable:

return false

case .reachableViaWiFi, .reachableViaWWAN:

return true

}

}

How to use the Reachability?

Using your new Reachability class is really straightforward: you create a new instance, and start the notifier. Then, if want to control the state of the UI depending on the reachability, you can register your view controller to the reachability notification and make it react to reachability changes.

Let's build a very simple example to test our Reachability class. I will change the color of the viewcontroller's view to green when there is connection to the Internet. Instead, the color of the view will change to red if there is no Internet connected.

Open the ViewController.swift file and add a new property to this class:

import UIKit

class ViewController:UIViewController {

var reachability: Reachability? = Reachability.reachabilityForInternetConnection()

In the viewDidLoad() method we add the view controller as an observer of the reachability notification.

override func viewDidLoad() {

super.viewDidLoad()

NotificationCenter.default.addObserver(self, selector: #selector(reachabilityDidChange(_:)), name: NSNotification.Name(rawValue: ReachabilityDidChangeNotificationName), object: nil)

_ = reachability?.startNotifier()

}

In the deinit we stop the notifier:

deinit {

NotificationCenter.default.removeObserver(self)

reachability?.stopNotifier()

}

Whenever the view of this view controller appears on the screen, we check for the reachability:

override func viewWillAppear(_ animated: Bool) {

super.viewWillAppear(animated)

checkReachability()

}

func checkReachability() {

guardlet r = reachability else { return }

if r.isReachable  {

view.backgroundColor = UIColor.green

} else {

view.backgroundColor = UIColor.red

}

}

When we receive a notification, the view controller executes the following method:

func reachabilityDidChange(_ notification: Notification) {

checkReachability()

}

Of course you can also use it with a domain address. Just use the corresponding initializer, for example:

var reachability = Reachability(hostName: "www.apple.com")

Conclusion

I hope that now you have a better understanding of how the SystemConfiguration framework works.

Enjoy learning!!!

Geppy Parziale( @geppy ) is cofounder of InvasiveCode . He has developed many iOS applications and taught iOS development to many engineers around the world since 2008. He worked at Apple as iOS and OS X Engineer in the Core Recognition team. He has developed several iOS and OS X apps and frameworks for Apple, and many of his development projects are top-grossing iOS apps that are featured in the App Store. Geppy is an expert in computer vision and machine learning.

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

推荐阅读更多精彩内容