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.
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.
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; // secondsSet 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();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)On iOS 13+, Apple introduced allowsExpensiveNetworkAccess and allowsConstrainedNetworkAccess as a new configuration properties.
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
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
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.