WWDC2019 — Advances in Networking Part 1

2 Main frameworks for networks on iOS: NSURLSession and Network.framework.

Low Data Mode

New feature in iOS13. Intended to use less data on a particular network, such as a congested on-airplane Wi-Fi network. Explicit signal to your users and to the application to use less data. Works on wifi and cell network. Disables background app refresh and discretionary tasks.

Suggested techniques for conforming to low data mode:

  • Reducing image quality.
  • Reduce pre-fetching. (of unused or rarely used resources).
  • Sync less often, using locally cached data more heavily. (Reduce the sync rate or interval)
  • Mark background tasks as discretionary (making them executable later, and not immediately).
  • Disable auto-play.
  • Do not block user-initiated work, even though low data mode is on.

NSURLSession now has:

  • allowsConstrainedNetworkAccess = false (true by default)
  • On failure with: error.networkUnavailableReason == .constrained (try Low Data Mode alternative).
  • allowsExpensiveNetworkAccess (property set by the system to cellular network). Apple encourages us to stop using this property and instead use the new allowsConstrainedNetworkAccess mode (which is user-initiated). If you are already using the “expensive” or “cellular” checks, however, and want to stick with these, Apple encourages using “expensive” over “cellular” checks, because it’s more flexible.

Network.framework now has:

  • Set prohibitConstrainedPaths on NWParameters
  • Check isConstrained on MWPath
  • Handle path updates

“Combine” in URLSession

“Combine” is a new declarative asynchronous programming framework in Swift. A declarative API for processing values over time. Consists of Publishers, Operators and Subscribers, that flows like this:

Publisher -> value -> .operator() -> value -> .operator() -> Subscriber

Then the Subscriber returns the request back to the Publisher:

Subscriber -> request -> .operator()-> request ->.operator() -> Publisher

.debounce(0.2) // delays a search by 0.2 seconds ignoring previous queries and only executing the last one. Waits for a 0.2 sec period of inactivity before executing a search.

.removeDuplicates() // will not repeat a search if a duplicate search was performed.

.filter { $0.count ≥ 3 } // drops all strings shorter than 3 characters. Will not search if shorter than 3 characters.

.map(toSearchURL) // maps the value into a URL

.sink() // subscribes to input and receives a URL to start a search.

Makes your conditional asynchronous code linear and compose-able.

In the example given, when in Low Data Mode, images returned are smaller size black-and-white images, and with Low Data Mode Off, they are larger, full-color images.

Without using combine, we would have to receive an error back when fetching the full-version image, and perform another network request for the low res image version. This code has a lot of duplication: checking the status code twice. We are also not guaranteed to have the same cell in the example below when inside DispatchQueue.main.async, because it could’ve been recycled by UIKit.

The same example implemented with Combine:

We make the cell a subscriber of a new dataTaskPublisher. We handle all events of the publisher with .tryCatch (error case), .tryMap (success case), .replaceError, .receive, and .assign. This gets rid of the redundant code for high-res vs low-res images. The returned dataTaskPublisher for the low res image is re-injected into the same structure to undergo the same processing logic as the full-version image:

Retry logic previously would have to have you manage that logic yourself through a state machine, and a recursive call. Now we can add:

.retry(1) //which would catch the error and restart the chain of operators for you. Retry also will handle any error such as a 500 from a flaky server, and retry the operation automatically. Use retry with caution with a very low number, because networking operations are expensive. Also be careful blindly retrying, and make sure your transaction calls are idempotent (will not mutate data state on the server more than once).

Combine is very well suited to adapting to low data mode with the .tryCatch, .retry, and .tryMap operators.

Here’s a way to fail-over onto a secondary URL dedicated for low-data mode:

WebSocket

URLSessionWebSocketTask

#1 requested feature from the networking survey.

Allows two-way communication over a TLS/TCP connection, allowing you to build apps like 2-way chat.

Works with Firewalls, CDNs, and through proxies.

Previously, in HTTP1.1 Long-Polling, we might implement a chat application by making a GET request, and receiving a 200 code back immediately, but no body, because a body is not available yet. The body is then sent at a later point in time, and the client issues the next GET request, receiving the immediate 200 again, and keeps waiting for the next message body.

Long-Polling has some drawbacks: complexity on the server, and use of full HTTP response/request which have significant overhead.

Websockets works with the client first issuing an “upgrade to websockets” request to the server. The server responds with a “101 Switching Protocols” response. A bidirectional stream is established. Both parties can now send messages without any HTTP overhead and in any order over this stream.

URLSessionWebSocketTask (In NSURLSession):

  • handles handshaking for you
  • works with existing URL session.
  • Use .send() and .receive() methods to communicate with the server.

Network.framework also gets WebSocket support through: NWProtocolWebSocket:

  • Uses same send and receive methods for sockets as last year Network.framework version.
  • Supports partial message sending and receiving.
  • Can be extended for peer to peer communication.

If we wanted to have the data update in our app automatically (without the user having to pull-to-refresh), here’s how our server can send a message to all our clients to notify them of updated data:

Setting up the iOS client to connect to the socket:

Implementing readMessage() :

This allows us bidirectional communication without any HTTP overhead.

Mobility Improvements

Currently when users walk outside their house, WiFi condition degrades and your web requests start to fail or time out. So most users realize this and turn off wifi, forcing the device onto the cell network. It would be great if the phone did this automatically.

As an additional challenge, it’s difficult and unreliable for the phone itself to assess the quality of the signal, because short term interference like walls can pose as an issue, when the phone is moved around. This makes “mobility” a challenge for the phone to decide when to switch to a cell network from wifi.

In iOS7, Multipath TCP was introduced allowing both WIFI and cell to be used at the same time. This protocol however requres both the client and the server to adopt it. Siri has been using this protocol since iOS7.

In iOS9, Wifi Assist was implemented. It works by opening a second cellular connection if the wifi connection is deemed as that of poor quality. Using regular “high level” networking APIs in iOS already gives you WiFi Assist automatically. No server configuration is needed. However this solution fails if the connection establishes fine on WiFi, but shortly after the signal still degrades, leaving you in a waiting state. (edge case)

In iOS11 Apple opened up Multipath TCP (that Siri already used since iOS9), for everyone to use in NSURLSession and Network.framework (handover / interactive mode).

iOS 13 — many frameworks and daemons have been improved for Mobility, including: Wi-Fi Assist and Multipath transports.

WiFi Assist has been upgraded with cross-layer mobility detection, where various system areas feed into the consensus regarding overall wifi condition (from lower to higher layers):

Using URLSession and Network.framework already gives us the new WiFi assist benefits.

Existing SCNetworkReachability preflight checks are error prone because between the time of the reachability check and the execution of the request, conditions may have changed. Can use helpers allowsExpensiveNetworkAccess = false (will not send your request on Cell network, only WiFi).

Multipath Transports is now enabled for Apple Maps and Apple Music in iOS13. (mobility frequently was an issue when leaving the house to go somewhere).

We can now enable Multipath Transports by setting: multipathServiceType URLSessionConfiguration and Network.framework. This still requires appropriate server side configuration in order to work. Server config instructions here: https://multipath-tcp.org

Other topics covered in “Advances in Networking, Part 2”: Bonjour, Building Framing Protocols, Collecting Metrics, Best Practices and Status Updates.

Alexei: App maker, interested in many topics.

Written by

Follow me for summaries of top-selling business books. My programming career book on Amazon: “Technology Stories - 125 Secrets for a Programming Career”