Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Network Reachability With Swift

DZone's Guide to

Network Reachability With Swift

Learn to handle network reachability so your app can perform or postpone actions depending on whether there's an internet connection.

· Mobile Zone ·
Free Resource

I had been using the Alamofire NetworkReachabilityManager class to check the network state of the user’s device—and suddenly I have found a bug because of an edge-case scenario. At that point, I decided to learn well how this class works and how to contribute to fixing the issue.

The result is in this Pull Request: Alamofire #2060.

I think that network reachability is an interesting topic to know, therefore I want to share with you how it works.

Happy reading!

Contents

What Is Network Reachability?

Network reachability is the network state of the user’s device. We can use it to understand if the device is offline or online using either WiFi or mobile data.

To read this information, we can use the interface of SCNetworkReachability provided by the framework SystemConfiguration. We can use it to read the network information both synchronously and asynchronously.

Let’s start step by step using this interface.

Instantiation

Import the framework SystemConfiguration merely with:

import SystemConfiguration

The first step is the instantiation of a SCNetworkReachability object. There are mainly two ways to do it:

1. Using a hostname:

We can use the function SCNetworkReachabilityCreateWithName which needs a hostname as argument:

let reachability = SCNetworkReachabilityCreateWithName(nil, "www.google.com")

// Or also with localhost
let reachability = SCNetworkReachabilityCreateWithName(nil, "localhost")

The first parameter is the allocator; we can pass nil to use the default one. SCNetworkReachabilityCreateWithName returns an optional value, therefore we have to unwrap it.

2. Using a network address reference:

// Initializes the socket IPv4 address struct
var address = sockaddr_in()
address.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
address.sin_family = sa_family_t(AF_INET)

// Passes the reference of the struct
let reachability = withUnsafePointer(to: &address, { pointer in
    // Converts to a generic socket address
    return pointer.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout<sockaddr>.size) {
        // $0 is the pointer to `sockaddr`
        return SCNetworkReachabilityCreateWithAddress(nil, $0)
    }
})

Network Flags

Once we have a SCNetworkReachability object, we are ready to use the network state information.

SCNetworkReachability provides this information with a set of flags—SCNetworkReachabilityFlags.

Here is the list of flags from the official documentation:

  • transientConnection: The specified node name or address can be reached via a transient connection, such as PPP.
  • reachable: The specified node name or address can be reached using the current network configuration.
  • connectionRequired: The specified node name or address can be reached using the current network configuration, but a connection must first be established. If this flag is set, the kSCNetworkReachabilityFlagsConnectionOnTraffic flag, kSCNetworkReachabilityFlagsConnectionOnDemand flag, or kSCNetworkReachabilityFlagsIsWWAN flag is also typically set to indicate the type of connection required. If the user must manually make the connection, the kSCNetworkReachabilityFlagsInterventionRequired flag is also set.
  • connectionOnTraffic: The specified node name or address can be reached using the current network configuration, but a connection must first be established. Any traffic directed to the specified name or address will initiate the connection.
  • interventionRequired: The specified node name or address can be reached using the current network configuration, but a connection must first be established.
  • connectionOnDemand: The specified node name or address can be reached using the current network configuration, but a connection must first be established. The connection will be established “On Demand” by the CFSocketStream programming interface (see CFStream Socket Additions for information on this). Other functions will not establish the connection.
  • isLocalAddress: The specified node name or address is one that is associated with a network interface on the current system.
  • isDirect: Network traffic to the specified node name or address will not go through a gateway, but is routed directly to one of the interfaces in the system.
  • isWWAN: The specified node name or address can be reached via a cellular connection, such as EDGE or GPRS.
  • connectionAutomatic: The specified node name or address can be reached using the current network configuration, but a connection must first be established. Any traffic directed to the specified name or address will initiate the connection. This flag is a synonym forconnectionOnTraffic.

We can easily read these flags using the function SCNetworkReachabilityGetFlags which wants two parameters:
– A SCNetworkReachability object.
– An empty SCNetworkReachabilityFlags object passed by reference—in this way the internal implementation of SCNetworkReachabilityGetFlags adds the flags to this object.

var flags = SCNetworkReachabilityFlags()
SCNetworkReachabilityGetFlags(reachability!, &flags)
// Now `flags` has the right data set by `SCNetworkReachabilityGetFlags`

Then, since flags is a Set, we can check if a flag is available with the method contains:

let isReachable: Bool = flags.contains(.reachable)

Using Synchronously

At this point, we have an instance of SCNetworkReachability and we know how to read the flags. We are ready to check if the user’s device has internet connection.

To get this information, we can read the content of SCNetworkReachabilityFlags with the following method:

func isNetworkReachable(with flags: SCNetworkReachabilityFlags) -> Bool {
    let isReachable = flags.contains(.reachable)
    let needsConnection = flags.contains(.connectionRequired)
    let canConnectAutomatically = flags.contains(.connectionOnDemand) || flags.contains(.connectionOnTraffic)
    let canConnectWithoutUserInteraction = canConnectAutomatically && !flags.contains(.interventionRequired)

    return isReachable && (!needsConnection || canConnectWithoutUserInteraction)

If you have some doubts about the meaning of some flags, you can check the flags list in “Network Flags”.

And we can use the function above like this:

// Optional binding since `SCNetworkReachabilityCreateWithName` return an optional object
guard let reachability = SCNetworkReachabilityCreateWithName(nil, "www.google.com") else { return }

var flags = SCNetworkReachabilityFlags()
SCNetworkReachabilityGetFlags(reachability, &flags)

if !isNetworkReachable(with: flags) {
    // Device doesn't have internet connection
    return
}

#if os(iOS)
    // It's available just for iOS because it's checking if the device is using mobile data
    if flags.contains(.isWWAN) {
        // Device is using mobile data
    }
#endif

// At this point we are sure that the device is using Wifi since it's online and without using mobile data

The example above is completely synchronous. This means that we don’t have to wait for any completion handler to get the information which we’re looking for.

Using Asynchronously

There are applications where the synchronous information is not enough. We may need an asynchronous callback which says when the network state is changed.

Fortunately, SCNetworkReachability provides the possibility to set a listener of network changes. We can achieve it with the following example:

class Handler {

    private let reachability = SCNetworkReachabilityCreateWithName(nil, "www.google.com")

    // Queue where the `SCNetworkReachability` callbacks run
    private let queue = DispatchQueue.main

    // We use it to keep a backup of the last flags read.
    private var currentReachabilityFlags: SCNetworkReachabilityFlags?

    // Flag used to avoid starting listening if we are already listening
    private var isListening = false

    // Starts listening
    func start() {
        // Checks if we are already listening
        guard !isListening else { return }

        // Optional binding since `SCNetworkReachabilityCreateWithName` returns an optional object
        guard let reachability = reachability else { return }

        // Creates a context
        var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
        // Sets `self` as listener object
        context.info = UnsafeMutableRawPointer(Unmanaged<Handler>.passUnretained(self).toOpaque())

        let callbackClosure: SCNetworkReachabilityCallBack? = {
            (reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) in
            guard let info = info else { return }

            // Gets the `Handler` object from the context info
            let handler = Unmanaged<Handler>.fromOpaque(info).takeUnretainedValue()

            DispatchQueue.main.async {
                handler.checkReachability(flags: flags)
            }
        }

        // Registers the callback. `callbackClosure` is the closure where we manage the callback implementation
        if !SCNetworkReachabilitySetCallback(reachability, callbackClosure, &context) {
            // Not able to set the callback
        }

        // Sets the dispatch queue which is `DispatchQueue.main` for this example. It can be also a background queue
        if !SCNetworkReachabilitySetDispatchQueue(reachability, queue) {
            // Not able to set the queue
        }

        // Runs the first time to set the current flags
        queue.async {
            // Resets the flags stored, in this way `checkReachability` will set the new ones
            self.currentReachabilityFlags = nil

            // Reads the new flags
            var flags = SCNetworkReachabilityFlags()
            SCNetworkReachabilityGetFlags(reachability, &flags)

            self.checkReachability(flags: flags)
        }

        isListening = true
    }

    // Called inside `callbackClosure`
    private func checkReachability(flags: SCNetworkReachabilityFlags) {
        if currentReachabilityFlags != flags {
            // �� Network state is changed ��

            // Stores the new flags
            currentReachabilityFlags = flags
        }
    }

    // Stops listening
    func stop() {
        // Skips if we are not listening
        // Optional binding since `SCNetworkReachabilityCreateWithName` returns an optional object
        guard isListening,
            let reachability = reachability
            else { return }

        // Remove callback and dispatch queue
        SCNetworkReachabilitySetCallback(reachability, nil, nil)
        SCNetworkReachabilitySetDispatchQueue(reachability, nil)

        isListening = false
    }
}

Thanks to the Handler class, we can start/stop listening with the methods start/stop.

Note: To keep this example as plain as possible, checkReachability(flags:) doesn’t check if the network is reachable but just if the flags are changed. In a real scenario, we may want to check also if the device has an internet connection using the method isNetworkReachable used in “Using Synchronously.”

Avoiding Manual Implementation

If we want to avoid bothering yourself with making a manual implementation, we can use an open source library which provides a high-level interface to wrap SCNetworkReachability.

The most famous are:

Ios 11+

An example where we should use the network reachability may be an app which reads some information from a public API. If the user’s device doesn’t have an internet connection, trying to fetch the data doesn’t make sense. Instead, we should listen to the network state changes and start fetching the data once we receive the notification that the device has an internet connection available.

With iOS 11, Apple introduced the property waitsForConnectivity in the URLSessionConfiguration to wait for an internet connection before performing any network activity with URLSession. This means that with this property set to true you don’t need to bother yourself checking manually for the device’s connection and waiting for a valid internet connection. URLSession will do it under the hood.

Unfortunately, if we have to support also versions older than iOS 11—or if we perform network activities without using URLSession—we must continue doing it manually.

Conclusion

Most of the apps which we develop have to use an internet connection to transfer data. Therefore, we should properly handle the reachability state of the user’s device—to understand if we can perform or postpone some actions handling the missing connection. It can improve battery life and user experience.

References

To write this article, I used the implementation of both my Alamofire Pull Request and the open source Library Reachability.swift.

Topics:
mobile ,mobile app development ,swift ,ios

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}