IOS/OSX Messaging Using the Network Framework and Bonjour Service (no external server required!).

Today we’ll explore how to send messages between multiple Apple devices using the Network framework, without the need of having external server. We’ll also use Bonjour service to let clients discover the each others automatically.

Let’s start with the first task: discovering devices using Bonjour (https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/NetServices/Introduction.html)

To accomplish this task we’ll need a server a browser. The server does the advertising and handles clients, the browser finds available servers (listeners).

First of all, we need to add NSBonjourServices key to Info.plist (https://developer.apple.com/documentation/bundleresources/information_property_list/nsbonjourservices), and pass an array of Bonjour service types. In our case, we’ll define one type and we’ll use TCP protocol. The underscores in the type field are required!

<key>NSBonjourServices</key>
<array>
<string>
_superapp._tcp</string>
</array>

Next, we want to use any network available, preferably a local network, and to be able to use the local network on devices running IOS14 we need to add to Info.plist NSLocalNetworkUsageDescription key.

<key>NSLocalNetworkUsageDescription</key>
<string>
Why do we use Local Network????</string>

We want to be able to see logs for both server and client (or browser) simultaneously in the Console.app for that we’ll create a global helper function and we’ll stick it in AppDelegate.swift

To run multiple Console.app instances, to open up a new Console.app, in the terminal type:

open -n /System/Applications/Utilities/Console.app

Create server and start advertising

We use TCP protocol, we don’t use TLS (to keep the code super simple) and we use Bonjour service to advertise. As a name, we user “server” and as a type, we use the same type we added into Info.plist.

Before starting listener we need to add handlers otherwise it will not work.

Create a browser and browse for available servers using Bonjour

When creating NWBrowser it’s important to use the same type specified inside Info.plist and used in the NWListener. Also, we need to add handlers to the browser before starting it.

Testing discovering server using Bonjour.

We made both Browser and Server global so we can use them directly from SwiftUI View like:

Now let’s run the app on two devices, start the server on the first device and tap the “Client” button on the second one.

On my server device console shows:

listener.stateUpdateHandler waiting(POSIXErrorCode: Network is down)
listener.stateUpdateHandler ready

The client device shows:

browser.stateUpdateHandler ready2020-11-30 07:47:02.461367+0100 MultiConnect[36355:9740017] [MPLogging] browser.browseResultsChangedHandler result: Result(nw: server._superapp._tcp.local.@[en0], endpoint: server._superapp._tcplocal., interfaces: [en0], metadata: <none>)2020-11-30 07:47:02.724386+0100 MultiConnect[36355:9740017] [MPLogging] browser.browseResultsChangedHandler result: Result(nw: server._superapp._tcp.local.@[awdl0], endpoint: server._superapp._tcplocal., interfaces: [awdl0], metadata: <none>)

Yay! The client’s phone was able to discover the server.

Our second task: do something useful with our system (ie. send messages between devices). We’ll create a Client and Connection, to handle sending messages, we will start with Connection.swift

We will initiate the connection with either NWEndpoint (our client’s outgoing connection) or NWConnection (server’s incoming connection). We need to add stateUpdateHandler before starting the connection.

To test it, for now, we’ll just update the browser:

Update the server:

Let’s run it on two devices. We should see something like this in logs:

Currently, our server will let us connect only one device, we will update the code to handle multiple simultaneous connections.
First, create Client.swift which will be shared across the app instead of Connection.

Now, it will be the client role to connect to the server, not the browser.

Update Browser.swift:

The browser will discover available servers and pass the result to the client.

Update Server.swift

The server will now store an array of connections instead of having one connection available.

We also have a send function which will send a messages to all connected clients.

Let’s update the connection:

We’ve added send(_ message: String) and receive() functions. The receive() function is first called when the connection state is .ready. connection.receive(…) schedules a single receive completion handler, so we need to call receive() again inside the handler, to receive more messages.

To test the system lets add the “Send” button to our ContentView, which will trigger sending a message:

Let’s build and run the project on your devices.

Yay (v2)!!!! We can send and receive messages between our server and two clients.

We don’t handle errors, and disconnections, because I wanted to make the code as short as possible, but of course in the production app it would have to be handled.

Here’s a WWDC talk about using Network framework:

https://developer.apple.com/videos/play/wwdc2019/713/

Here’s a sample project using Network framework to play Tic Tac Toe game, it’s a bit more advanced than my blog post project :)

Here’s link to final version of the sample code:

Please check my other posts, show some love (👏), follow me on twitter: http://twitter.com/boramaapps