Building Peer-to-Peer Sessions: Sending and Receiving Data with Multipeer Connectivity

Building Peer-to-Peer Sessions: Sending and Receiving Data with Multipeer Connectivity

Learn how to use the Multipeer Connectivity framework to connect to other devices locally and send and receive data between them.

To transmit data between peers, receive and process messages in real-time, use the Multipeer Connectivity framework, which enables nearby devices to communicate peer-to-peer without relying on central servers.

If this is your first time using this framework, check the article Getting Started with Multipeer Connectivity in Swift and the article Building Peer-to-Peer Sessions: Advertising and Browsing Devices.

Set up a session

Two properties are needed to initiate data sending or receiving operations, one to identify the device and one to set a communication channel between them:

  • peerID: represents the identity of this device within the session;
  • session: the primary communication channel between connected devices.
class DataSessionManager: NSObject, ObservableObject {

    private let peerID = MCPeerID(displayName: UIDevice.current.name)
    private var session: MCSession!
    
}

When initializing the class responsible for handling the communication between devices, create a secure session with MCSession associated with the device peerID.

To use the system's default identity, suitable for most applications, keep the securityIdentity parameter as nil and encryptionPreference as required to ensure that all transmitted data is encrypted.

override init() {
    super.init()
    session = MCSession(
        peer: peerID,
        securityIdentity: nil,
        encryptionPreference: .required
    )
}

Sending information

For this example, the function that sends data will convert a String message to the Data type, encoded as UTF-8, to make it compatible with the session sending protocol.

Using the method send(_:toPeers:with:) the data is sent to all connected peers, represented by the session property connectedPeers. Since sending the information has the possibility of failure, error handling is necessary.

class DataSessionManager: NSObject, ObservableObject {

    func sendMessage(_ message: String) {
        guard !session.connectedPeers.isEmpty else { return }

        if let data = message.data(using: .utf8) {
            do {
                try session.send(data, toPeers: session.connectedPeers, with: .reliable)
            } catch {
                print("Error \(error.localizedDescription)")
            }
        }
    }
    
}

Tracking state changes

To track state changes on a session, we need to implement the MCSessionDelegate protocol.

extension DataSessionManager: MCSessionDelegate {
    
    func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
        // TODO
    }
    
    func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
        // TODO
    }
    
    func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {
        // TODO
    }
    
    func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) {
        // TODO
    }
    
    func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: (any Error)?) {
        // TODO
    }
    
}

Once you do that, assign your class to be the delegate of the session object in your class initializer. This way, you will be able to track all the changes in the session and act accordingly.

override init() {
    super.init()
    session = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .required)
    
    session.delegate = self
}

Receiving information

Among the various functions of the MCSessionDelegate protocol, the session(_:didReceive:fromPeer:) method is invoked when information is received from one of the connected peers in the session.

func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
    if let message = String(data: data, encoding: .utf8) {
        DispatchQueue.main.async {
            self.receivedMessages.append("\(peerID.displayName): \(message)")
        }
    }
}

In the example above, we convert the received Data object to a String value encoded in UTF-8 and then append the received message to an array that stores all the received messages.


Below is the full implementation of the example in the article:

import Foundation
import MultipeerConnectivity
import Observation

@Observable
class DataSessionManager: NSObject {
    
    private let serviceID = "p2p-data"
    private let peerID = MCPeerID(displayName: UIDevice.current.name)
    private var session: MCSession!
    
    var receivedMessages: [String] = []
    
    override init() {
        super.init()
        session = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .required)
        session.delegate = self
    }

    func sendMessage(_ message: String) {
        guard !session.connectedPeers.isEmpty else { return }
        if let data = message.data(using: .utf8) {
            do {
                try session.send(data, toPeers: session.connectedPeers, with: .reliable)
            } catch {
                print("Error \(error.localizedDescription)")
            }
        }
    }

    func invite(peer: MCPeerID, using browser: MCNearbyServiceBrowser) {
        browser.invitePeer(peer, to: session, withContext: nil, timeout: 10)
    }
}
extension DataSessionManager: MCSessionDelegate {
    func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {}
    
    func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
        if let message = String(data: data, encoding: .utf8) {
            DispatchQueue.main.async {
                self.receivedMessages.append("\(peerID.displayName): \(message)")
            }
        }
    }
    
    func session(_ session: MCSession,
                 didReceive stream: InputStream,
                 withName streamName: String,
                 fromPeer peerID: MCPeerID) {
        // TODO
    }
    
    func session(_ session: MCSession,
                 didStartReceivingResourceWithName resourceName: String,
                 fromPeer peerID: MCPeerID,
                 with progress: Progress) {
        // TODO
    }
    
    func session(_ session: MCSession,
                 didFinishReceivingResourceWithName resourceName: String,
                 fromPeer peerID: MCPeerID,
                 at localURL: URL?,
                 withError error: Error?) {
        // TODO
    }
}