So, you have decided to create iOS application with its watch companion application. And, you need to exchange some data between two of them. For this scenario Apple provides simple WatchConnectivity framework, that specifically designed for this type of scenarios. This framework does most of the heavy lifting and your goal is simply to implement the business logic.

In this article consists of two parts:

  • Part 1. Minimum you need to sync data.
  • Part 2. One of the approaches to encapsulate complex sync data logic.

Lets get started. To isolate all logic in one place lets create a service. First of all we need to import WatchConnectivity framework. Also, we need to implement WCSessionDelegate delegate protocol that defines methods for receiving messages from WCSession.

import Foundation
import WatchConnectivity

class SyncService : NSObject, WCSessionDelegate { }

Initialize

Now, lets initialize our service. The WCSession is the core object responsible for whole communication between iOS and WatchKit. At first we create instance of WCSession, then we assign our service as WCSessionDelegate delegates handler, and lastly we initialize our session.

private var session: WCSession = .default

init(session: WCSession = .default) {
    self.session = session

    super.init()

    self.session.delegate = self
    self.connect()
}

Connect

According to the WCSession documentation, before activating (connect) session, we need to make sure that current device can use WatchConnectivity. For that we need to call isSupported() method, if it is successful we simply activate the session.

func connect() {
    guard WCSession.isSupported() else {
        print("WCSession is not supported")
        return
    }
    
    session.activate()
}

Now, lets add minimum required handlers from WCSessionDelegate delegate protocol. We are going to reuse our service on both sides iOS and WatchOS. Because not all methods from WCSessionDelegate supported on WatchOS we need to use #if preprocessing directive to exclude some code from WatchOS version.

func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { }

#if os(iOS)
func sessionDidBecomeInactive(_ session: WCSession) { }

func sessionDidDeactivate(_ session: WCSession) { }
#endif

Where:

  • session: delegate method called when session has finished activating. Here you can check is session was activated or not, and handle errors if any.
  • sessionDidBecomeInactive: delegate method is called when session will stop communicating with the current Apple Watch.
  • sessionDidDeactivate: delegate method is called when communication with the Apple Watch has ended.

Send data

To send data from one device to another we need to use WCSession sendMessage method. Because, we are creating service, lets wrap it in our method that check if counterpart is available, before sending message.

func sendMessage(_ key: String, _ message: String, _ errorHandler: ((Error) -> Void)?) {
    if session.isReachable {
        session.sendMessage([key : message], replyHandler: nil) { (error) in
            print(error.localizedDescription)
            if let errorHandler = errorHandler {
                errorHandler(error)
            }
        }
    }
}

Where:

  • key: the message key, unique name that is used to identify message on other side.
  • message: the data we want to sent to other side. To send complex objects, just convert them to JSON format.
  • errorHandler: the error handler in case if message was not send.

Receive data

The last thing we need to do is to handle received messages on the counterpart side. For that we also need to implement one more method from WCSessionDelegate delegate protocol.

var dataReceived: ((String, Any) -> Void)?

func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
    guard dataReceived != nil else {
        print("Received data, but 'dataReceived' handler is not provided")
        return
    }
        
    DispatchQueue.main.async {
        if let dataReceived = dataReceived {
            for pair in message {
                dataReceived(pair.key, pair.value)
            } 
        }
    }
}

Where:

  • session: current session object.
  • message: actual received message from counterpart app. Where the dictionary pair key is a message key, and value is the message itself.
  • dataReceived: your data handler where you are going to handle the received messages.

Usage

Basically, that is the bearer minimum what you need to send/receive messages between iOS and Apple Watch. To test this service you need to start iOS and watch simulators all together.

var syncService = SyncService()
syncService.dataReceived = { (key, message) in 
    // handle message based on key
}

syncService.sendMessage("dataSyncKey", "Some data to sync", { error in })

Also see Source Code section for more practical usage.

Summary

Lets combine all together to get full picture.

import Foundation
import WatchConnectivity

class SyncService : NSObject, WCSessionDelegate {
    private var session: WCSession = .default
    var dataReceived: ((String, Any) -> Void)?
    
    init(session: WCSession = .default) {
        self.session = session

        super.init()

        self.session.delegate = self
        self.connect()
    }
    
    func connect(){
        guard WCSession.isSupported() else {
            print("WCSession is not supported")
            return
        }
        
        session.activate()
    }

    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { }

    #if os(iOS)
    func sessionDidBecomeInactive(_ session: WCSession) { }

    func sessionDidDeactivate(_ session: WCSession) { }
    #endif

    func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
        guard dataReceived != nil else {
            print("Received data, but 'dataReceived' handler is not provided")
            return
        }
        
        DispatchQueue.main.async {
            if let dataReceived = self.dataReceived {
                for pair in message {
                    dataReceived(pair.key, pair.value)
                }
            }
        }
    }

    func sendMessage(_ key: String, _ message: String, _ errorHandler: ((Error) -> Void)?) {
        if session.isReachable {
            session.sendMessage([key : message], replyHandler: nil) { (error) in
                print(error.localizedDescription)
                if let errorHandler = errorHandler {
                    errorHandler(error)
                }
            }
        }
    }
}

Source Code

The sample application for this article you can find here .