Network Reachability in Swift

Keywords: network Swift Attribute Mobile

The vast majority of mobile phone applications need to request data or update data from the background host or server through the network at a certain time. However, network connections are not always available, and disconnection may lead to unavailability at any time. In order to understand this problem, we can use _____________ SCNetworkReachability API Interface to get the current network status of the system and check whether the application can connect to the background server.

The SCNetwork Reachability interface is part of the Core Foundation framework and is implemented in C language. For some developers, the SCNetwork Reachability interface cannot be invoked directly in Swift.

Apple provides some sample code encapsulating a class called Reachability, which is an Objective-C encapsulated in the scnetworkreachability API.

Reachability class encapsulates the network connection in a friendly way. It can check whether the network has been connected or disconnected by a convenient way. When the network state changes, you can also inform the NSNotification Center by injecting your object into the NSNotification Center.

You can be there. Sample project provided by Apple Find the Reachability class.

If you are familiar with Reachability, you only need to use the latest version of the update code (version 4.2 released on November 11, 2015), which fixes several BUG s that may cause memory leaks.

Apple has already provided sample code for the Objective-C version, but many developers have a demand for the Swift version, so we'll talk about the NetWork Reachability API in Swift in this article. We will step by step explain how it works and use Swift 3.0 to implement our own Reachability class.

Description of SCNetwork Reachability API

The SCNetwork Reachability API provides a synchronous method to determine whether a network is connected. This synchronization method allows us to obtain the current network state by calling the SCNetwork Reachability GetFlags function. The second parameter of the function is a pointer to memory, which serves as an identification parameter of the network connection status. This parameter also contains some additional information, such as whether the network connection is automatically established or caused by user intervention.

The SCNetwork Reachability API provides asynchronous methods as well as synchronous methods. In order to implement this method, we have to call the SCNetwork Reachability object iteratively and provide a callback function to broadcast notifications when the remote server connection status changes.

Implementing with Swift

Start Xcode 8 and create a new Swift Single View Application project named Reachability Example, add a new swift file named Reachability.swift to the project, add such as import declaration:

import SystemConfiguration

We want to notify our app when there are three changes in the network (change: we want to notify our app when there are three changes in the network).

1. When the app loses its connection (change: when the app does not connect to the network)

2. When app is connected via wifi

3. When app is connected via WWAN

We will send a notification containing the status of the network connection. Next we will name the notification and define three possible network states:

let ReachabilityDidChangeNotificationName = "ReachabilityDidChangeNotification"
enum ReachabilityStatus {
case notReachable
case reachableViaWiFi
case reachableViaWWAN
}

Add an attribute to this class to save the SCNetwork Reachability object:

private var networkReachability: SCNetworkReachability?

To monitor whether the current server can connect, we create an initialization method that passes in the domain name as a parameter and initializes it through the SCNetwork Reachability CreateWithName function.

SCNetwork Reachability object. If SCNetwork Reachability initialization fails, it returns nil, so we create a failable initializer:

init?(hostName: String) {
    networkReachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, (hostName as NSString).UTF8String)
    super.init()
    if networkReachability == nil {
    return nil
    }
}

In order to create a reachability object based on the ip network address, we need to implement another initialization method. In this case we will use

SCNetwork Reachability CreateWithAddress function. Since this function requires a pointer to the network address, we call it the withUnsafe Pointer function. In this case, as we mentioned earlier, the return value of the function may be nil, so the init method can fail.

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 two class methods. The first method builds an instance of reachability to control the network connection. The second method is to detect if we are connected to a local wifi. Both methods must use the network address to use the construction method. In the case of local WIFI, address 169.254.0.0 is defined as IN_LINKLOCAL NETNUM.

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 to define an open notification and a close notification method, and define an attribute to identify whether the notification state is currently open or closed:

private var notifying: Bool = false

Before opening the notification, check whether the notification is open. Then get the SCNetwork Reachability Context container and assign the value of self to the info attribute of the context object. The callback function is then set to pass context (when the callback function is called, the reference pointer to self contained in the info parameter will be passed to the block data as the third parameter). If the callback function is set successfully, we can manage the reference to network reachability in the run loop.

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)
            }
        }
    }, &context) == true else { return false }
 
    guard SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode.rawValue) == true else { return false }
 
    notifying = true
    return notifying
}

Stop notifying, we just need to remove the reference management of network reachability from the run loop.

func stopNotifier() {
    if let reachability = networkReachability, notifying == true {
        SCNetworkReachabilityUnscheduleFromRunLoop(reachability, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode as! CFString)
        notifying = false
    }
}

Ensure that closure notifications are closed before Reachability is destroyed:

deinit {
    stopNotifier()
}

To get the network connection status, we define a flags attribute to get the SCNetwork Reachability object:

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 created a function that returns the network state based on the value passed by flags (the link state represented by the flags value is explained in the commentary to 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 understand the meaning, usage and differences of each flag, please read Apple Documents .

Finally, we create a function to determine the current network connection status, and ultimately check the connection through a bool variable:

var isReachable: Bool {
    switch currentReachabilityStatus {
    case .notReachable:
        return false
    case .reachableViaWiFi, .reachableViaWWAN:
        return true
    }
}

How to use Reachability?

Using the new Reachability is very straightforward: you create a Reachability instance and start the notification. Then, if you want to control the display of the state on the UI according to the state returned by the reachability. You can inject your view controller with reachability notification and display the reachability status changes directly on the view.

Let's use a simple sample code to determine our reachability class. If I connect to the network, I can change the color of view control to green. When disconnected, we change the color of view controller to red.

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

import UIKit
 
class ViewController: UIViewController {
 
    var reachability: Reachability? = Reachability.reachabilityForInternetConnection()

We add an observer to a view controller viewDidLoad() method for reachability notification

    override func viewDidLoad() {
        super.viewDidLoad()
     
        NotificationCenter.default.addObserver(self, selector: #selector(reachabilityDidChange(_:)), name: NSNotification.Name(rawValue: ReachabilityDidChangeNotificationName), object: nil)
     
        _ = reachability?.startNotifier()
    }

In deinit we close the notice:

  deinit {
        NotificationCenter.default.removeObserver(self)
        reachability?.stopNotifier()
    }

We check reachability to determine whether the controller displays green.

  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 control performs the following methods:

     func reachabilityDidChange(_ notification: Notification) {
         checkReachability()
     }

Of course, you can use the domain name address to call the corresponding constructor, as follows:

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

Summary:

Now, I hope you have a better understanding of how the System Configuration framework works.

Posted by gumby51 on Fri, 17 May 2019 14:35:20 -0700