← Back to Blog

Why connectivity check is important and how to get it right

2021-01-08·4 min read
SwiftiOSNetworkingAI / MLTutorial

Have you ever use an app while you have an internet connection, but the app tells you "Looks like you are offline!"? So here's my note on Network Reachablity.

Usually, what I've seen in practice, some of us will perform connectivity check before doing any URL request. But actually we should not perform connectivity check before making URL request because there's no reliable way to check for connectivity without actually trying to perform the request.

SCNetworkReachability

It's common in iOS development we use SCNetworkReachability to determine whether we should make a network request or not. But, this actually not reliable and could lead to bad user experience.

SCNetworkReachability API is not intended for use as a preflight mechanism to determine network connectivity. We determine network connectivity by attempting to connect. If the connection fails, consult the SCNetworkReachability API to help diagnose the cause of the failure.

So, what's the right way? Set request constraints, make the request, and handle the result.

URLSessionConfiguration

For example on iOS 11+, Apple provide waitsForConnectivity flag to URLSessionConfiguration. Set it to true, so the session will start the task again when connection is back.

  let configuration = URLSessionConfiguration.default
  if #available(iOS11, *) {
    configuration.waitsForConnectivity = true
  }
  let session = URLSession(configuration: configuration)

However, if the network is available and drop during the request, it will return the regular connectivity error callback and the session won't automatically retry. What we need to do next is either ask the user to retry or schedule an automatic retry.

Customize the timeout value if needed, the default is set to 7 days

configuration.timeoutIntervalForResource = 600; // seconds

Set a session delegate to be notified when the session is waiting for connectivity

  let session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)

Then, implement the taskIsWaitingForConnectivity delegate method

  func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {
    // For example, update the UI to notify the customer (with a cancel button)
    // or activate an interactive offline- or cellular-only-mode instead of blocking the UI
  }

And make sure to invoke invalidateAndCancel because the session object keeps a strong reference to the delegate until the app exits or explicitly invalidates the session. Without it, the app will leaks memory until it exits.

session.invalidateAndCancel();

Cellular connection

Set allowsCellularAccess flag in URLRequest or URLSessionConfiguration to check cellular connection. If set to false, it will prevent requests over cellular connection

  // set allowsCellularAccess in URLRequest
  var request = URLRequest(url: URL(string: "https://www.masrinastudio.com")!)
  request.allowsCellularAccess = true
 
  // set allowsCellularAccess in URLSessionConfiguration
  let configuration = URLSessionConfiguration.default
  configuration.allowsCellularAccess = true
  let session = URLSession(configuration: configuration)

Expensive Network Access and Low Data Mode

On iOS 13+, Apple introduced allowsExpensiveNetworkAccess and allowsConstrainedNetworkAccess as a new configuration properties.

  • allowsExpensiveNetworkAccess is to indicates whether connections may use a network interface that the system considers expensive.

iOS 13 considers most cellular networks and personal hotspots expensive. If there are no nonexpensive network interfaces available and the session’s allowsExpensiveNetworkAccess property is NO, any task created from the session fails. In this case, the error provided when the task fails has a networkUnavailableReason property whose value is NSURLErrorNetworkUnavailableReasonExpensive. — Apple developer documentation

  • allowsConstrainedNetworkAccess is to control whether connections may use the network when the user has specified Low Data Mode.

This property controls a URL session’s behavior when the user turns on Low Data Mode. If there are no nonconstrained network interfaces available and the session’s allowsConstrainedNetworkAccess property is NO, any task created from the session fails. In this case, the error provided when the task fails has a networkUnavailableReason property whose value is NSURLErrorNetworkUnavailableReasonConstrained. - Apple developer documentation

Conclusion

Using constraints such as allowsCellularAccess or allowsExpensiveNetworkAccess gives you much better control. It's much easier to use. Once you start writing your apps this way, you'll wonder why you ever did pre-flight checks. And besides, pre-flight checks can never work reliably because they always have race conditions." — Stuart Cheshire, Advances in Networking, Part 2, WWDC 2019.

Do not block requests based on pre-flight checks, set appropriate constraints, make a request, handle the result.

If the connection fails, check the error status to know the cause of failure and respond accordingly. e.g. set automatic retry, try a less expensive request, or notify the user.

More helpful reading and references