Network Reachability in Swift
Almost every mobile app needs to connect to the Internet at some point to retrieve data from a host or service or upload new data to it. However, the Internet connection is not always available, and its availability can change at any time. To know the state of the system's current network and if a host or service is reachable through that network, we can use the SCNetworkReachability
API.
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.html
If 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 API
The 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 Swift
Launch 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:
1 2 |
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:
1 2 3 4 5 6 7 8 |
let ReachabilityDidChangeNotificationName = "ReachabilityDidChangeNotification" enum ReachabilityStatus { case notReachable case reachableViaWiFi case reachableViaWWAN } |
Let's now implement the Reachability
class.
1 2 3 4 |
class Reachability: NSObject { } |
Let's add to this class a property to store the SCNetworkReachability
object:
1 2 |
private var 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:
1 2 3 4 5 6 7 8 |
init?(hostName: String) { networkReachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, (hostName as 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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 } } |
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
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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 in <netinet/in.h> as 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:
1 2 |
private var 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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<AnyObject>.fromOpaque(currentInfo).takeUnretainedValue() if infoObject is Reachability { let networkReachability = infoObject as! Reachability NotificationCenter.default.post(name: Notification.Name(rawValue: ReachabilityDidChangeNotificationName), object: networkReachability) } } }, &context) == true else { return false } guard SCNetworkReachabilityScheduleWithRunLoop(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:
1 2 3 4 5 6 7 |
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:
1 2 3 4 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 |
private var 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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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:
1 2 3 4 5 6 7 8 9 |
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:
1 2 3 4 5 6 |
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.
1 2 3 4 5 6 7 8 |
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:
1 2 3 4 5 |
deinit { NotificationCenter.default.removeObserver(self) reachability?.stopNotifier() } |
Whenever the view of this view controller appears on the screen, we check for the reachability:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) checkReachability() } func checkReachability() { guard let 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:
1 2 3 4 |
func reachabilityDidChange(_ notification: Notification) { checkReachability() } |
Of course you can also use it with a domain address. Just use the corresponding initializer, for example:
1 2 |
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 (@geppyp) 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.
Thanks for this. It's people like you that make iOS development a joy. How can I buy you a beer?
Thank you so much for reading our blog. We are glad that you liked this post.
No need to buy us a beer. You comment here is really appreciated.
This is excellent and thank you for your efforts. However, I find an error in the stopNotifier method that causes a crash. I had to change one line to this to get it to work: SCNetworkReachabilityUnscheduleFromRunLoop(reachability, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode.rawValue).
Thanks, that's what I needed!