Multipeer Connectivity ist eine Alternative zum üblichen Datenaustauschformat. Anstatt Daten über WLAN oder Mobilfunknetz über einen Zwischenbroker auszutauschen, bei dem es sich normalerweise um einen Backend-Server handelt, bietet Multipeer Connectivity die Möglichkeit, Informationen zwischen mehreren Geräten in der Nähe ohne Zwischenhändler auszutauschen.
iPhone und iPad nutzen Wi-Fi- und Bluetooth-Technologie, während MacBook und Apple TV auf Wi-Fi und Ethernet angewiesen sind.
Von hier aus ergeben sich unmittelbar die Vor- und Nachteile dieser Technologie. Zu den Vorteilen zählen die Dezentralisierung und dementsprechend die Möglichkeit, Informationen ohne Zwischenhändler auszutauschen.
Nachteile: Die Freigabe ist auf WLAN- und Bluetooth-Abdeckung für iPhone und iPad bzw. WLAN und Ethernet für MacBook und Apple TV beschränkt. Mit anderen Worten: Der Informationsaustausch kann nur in unmittelbarer Nähe der Geräte erfolgen.
Die Integration der Multipeer-Konnektivität ist nicht kompliziert und umfasst die folgenden Schritte:
Projektvorgabe
Sichtbarkeit für andere Geräte einrichten
Nach sichtbaren Geräten in Reichweite suchen
Erstellen eines Gerätepaares für den Datenaustausch
Datenaustausch
Sehen wir uns jeden der oben genannten Schritte genauer an.
In dieser Phase muss das Projekt für die Implementierung der Multipeer-Konnektivität vorbereitet werden. Dazu müssen Sie zusätzliche Berechtigungen vom Benutzer einholen, um scannen zu können:
Info.plist
„Privacy – Local Network Usage Description“ mit einer Beschreibung des Verwendungszwecks hinzu;Info.plist
zusätzlich noch um folgende Zeilen ergänzt werden:
<key>NSBonjourServices</key> <array> <string>_nearby-devices._tcp</string> <string>_nearby-devices._upd</string> </array>
Es ist wichtig zu beachten, dass die Teilzeichenfolge nearby-devices
in diesem Kontext als Beispiel verwendet wird. In Ihrem Projekt muss dieser Schlüssel die folgenden Anforderungen erfüllen:
Es ist 1–15 Zeichen lang und umfasst als gültige Zeichen ASCII-Kleinbuchstaben, Zahlen und den Bindestrich. Es muss mindestens ein Buchstabe vorhanden sein und darf keine Bindestriche neben dem Buchstaben enthalten.
Nähere Informationen zu den Voraussetzungen__ können Sie hier__ nachlesen.
Als Kommunikationsprotokolle verwendet das Beispiel tcp
und upd
(ein zuverlässigeres und ein weniger zuverlässiges). Wenn Sie nicht wissen, welches Protokoll Sie benötigen, sollten Sie beide eingeben.
Die Organisation der Gerätesichtbarkeit für Multi-Peer-Verbindungen wird von MCNearbyServiceAdvertiser
implementiert. Lassen Sie uns eine Klasse erstellen, die für das Erkennen, Anzeigen und Teilen von Informationen zwischen Geräten verantwortlich ist.
import MultipeerConnectivity import SwiftUI class DeviceFinderViewModel: ObservableObject { private let advertiser: MCNearbyServiceAdvertiser private let session: MCSession private let serviceType = "nearby-devices" @Published var isAdvertised: Bool = false { didSet { isAdvertised ? advertiser.startAdvertisingPeer() : advertiser.stopAdvertisingPeer() } } init() { let peer = MCPeerID(displayName: UIDevice.current.name) session = MCSession(peer: peer) advertiser = MCNearbyServiceAdvertiser( peer: peer, discoveryInfo: nil, serviceType: serviceType ) } }
Der Kern des Multipeers ist eine MCSession
, die Ihnen die Verbindung und den Datenaustausch zwischen Geräten ermöglicht.
Der serviceType
ist der oben erwähnte Schlüssel, der zusammen mit den Austauschprotokollen zur Datei Info.plist
hinzugefügt wurde.
Mit der Eigenschaft isAdvertised
können Sie die Sichtbarkeit des Geräts mithilfe von Toggle
umschalten.
Die Gerätesichtbarkeitsprüfung für eine Multi-Peer-Verbindung wird von MCNearbyServiceBrowser
durchgeführt:
class DeviceFinderViewModel: NSObject, ObservableObject { ... private let browser: MCNearbyServiceBrowser ... @Published var peers: [PeerDevice] = [] ... override init() { ... browser = MCNearbyServiceBrowser(peer: peer, serviceType: serviceType) super.init() browser.delegate = self } func startBrowsing() { browser.startBrowsingForPeers() } func finishBrowsing() { browser.stopBrowsingForPeers() } } extension DeviceFinderViewModel: MCNearbyServiceBrowserDelegate { func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) { peers.append(PeerDevice(peerId: peerID)) } func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) { peers.removeAll(where: { $0.peerId == peerID }) } } struct PeerDevice: Identifiable, Hashable { let id = UUID() let peerId: MCPeerID }
Eine Liste aller sichtbaren Geräte wird in peers
gespeichert. Die MCNearbyServiceBrowser
Delegatmethoden fügen eine MCPeerID
hinzu oder entfernen sie, wenn ein Peer gefunden wird oder verloren geht.
Die Methoden startBrowsing
und finishBrowsing
werden verwendet, um mit der Erkennung sichtbarer Geräte zu beginnen, wenn der Bildschirm erscheint, bzw. die Suche zu beenden, nachdem der Bildschirm verschwindet.
Die folgende View
wird als Benutzeroberfläche verwendet:
struct ContentView: View { @StateObject var model = DeviceFinderViewModel() var body: some View { NavigationStack { List(model.peers) { peer in HStack { Image(systemName: "iphone.gen1") .imageScale(.large) .foregroundColor(.accentColor) Text(peer.peerId.displayName) .frame(maxWidth: .infinity, alignment: .leading) } .padding(.vertical, 5) } .onAppear { model.startBrowsing() } .onDisappear { model.finishBrowsing() } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Toggle("Press to be discoverable", isOn: $model.isAdvertised) .toggleStyle(.switch) } } } } }
Die Gerätesichtbarkeit wird durch den Toggle
aktiviert/deaktiviert.
Als Ergebnis sollte zu diesem Zeitpunkt die Erkennung und Anzeige von Geräten ordnungsgemäß funktionieren.
Die Delegatmethode MCNearbyServiceAdvertiserdidReceiveInvitationFromPeer
ist für das Senden einer Einladung zwischen zwei Geräten verantwortlich. Beide Geräte müssen in der Lage sein, diese Anforderung zu verarbeiten.
class DeviceFinderViewModel: NSObject, ObservableObject { ... @Published var permissionRequest: PermitionRequest? @Published var selectedPeer: PeerDevice? { didSet { connect() } } ... @Published var joinedPeer: [PeerDevice] = [] override init() { ... advertiser.delegate = self } func startBrowsing() { browser.startBrowsingForPeers() } func finishBrowsing() { browser.stopBrowsingForPeers() } func show(peerId: MCPeerID) { guard let first = peers.first(where: { $0.peerId == peerId }) else { return } joinedPeer.append(first) } private func connect() { guard let selectedPeer else { return } if session.connectedPeers.contains(selectedPeer.peerId) { joinedPeer.append(selectedPeer) } else { browser.invitePeer(selectedPeer.peerId, to: session, withContext: nil, timeout: 60) } } } extension DeviceFinderViewModel: MCNearbyServiceAdvertiserDelegate { func advertiser( _ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void ) { permissionRequest = PermitionRequest( peerId: peerID, onRequest: { [weak self] permission in invitationHandler(permission, permission ? self?.session : nil) } ) } } struct PermitionRequest: Identifiable { let id = UUID() let peerId: MCPeerID let onRequest: (Bool) -> Void }
Wenn selectedPeer
gesetzt ist, wird die Methode connect ausgelöst. Wenn dieser peer
in der Liste der vorhandenen peers
enthalten ist, wird er dem Array joinedPeer
hinzugefügt. In Zukunft wird diese Eigenschaft von der Benutzeroberfläche verarbeitet.
Wenn dieser Peer in der Sitzung nicht vorhanden ist, lädt der browser
dieses Gerät ein, ein Paar zu erstellen.
Anschließend wird die Methode didReceiveInvitationFromPeer
für das eingeladene Gerät ausgeführt. In unserem Fall wird nach dem Start von didReceiveInvitationFromPeer
ein permissionRequest
mit verzögertem Callback erstellt, der als Alarm auf dem eingeladenen Gerät angezeigt wird:
struct ContentView: View { @StateObject var model = DeviceFinderViewModel() var body: some View { NavigationStack { ... .alert(item: $model.permissionRequest, content: { request in Alert( title: Text("Do you want to join \(request.peerId.displayName)"), primaryButton: .default(Text("Yes"), action: { request.onRequest(true) model.show(peerId: request.peerId) }), secondaryButton: .cancel(Text("No"), action: { request.onRequest(false) }) ) }) ... } } }
Im Falle einer Genehmigung gibt didReceiveInvitationFromPeer
das Gerät zurück, das die Einladung, die Berechtigung und die Sitzung gesendet hat, wenn die Berechtigung erfolgreich war.
Als Ergebnis wird nach erfolgreicher Annahme der Einladung ein Paar erstellt:
Nach der Paarbildung ist MCSession
für den Datenaustausch zuständig:
import MultipeerConnectivity import Combine class DeviceFinderViewModel: NSObject, ObservableObject { ... @Published var messages: [String] = [] let messagePublisher = PassthroughSubject<String, Never>() var subscriptions = Set<AnyCancellable>() func send(string: String) { guard let data = string.data(using: .utf8) else { return } try? session.send(data, toPeers: [joinedPeer.last!.peerId], with: .reliable) messagePublisher.send(string) } override init() { ... session.delegate = self messagePublisher .receive(on: DispatchQueue.main) .sink { [weak self] in self?.messages.append($0) } .store(in: &subscriptions) } } extension DeviceFinderViewModel: MCSessionDelegate { func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { guard let last = joinedPeer.last, last.peerId == peerID, let message = String(data: data, encoding: .utf8) else { return } messagePublisher.send(message) } }
Die Methode func send(_ data: Data, toPeers peerIDs: [MCPeerID], with mode: MCSessionSendDataMode) throws
hilft beim Senden von Daten zwischen Peers.
Die Delegatmethode func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID)
wird auf dem Gerät ausgelöst, das die Nachricht empfangen hat.
Außerdem wird ein Zwischenherausgeber messagePublisher
zum Empfangen von Nachrichten verwendet, da die MCSession
Delegatmethoden in der DispatchQueue global()
ausgelöst werden.
Weitere Einzelheiten zum Multipeer Connectivity-Integrationsprototyp finden Sie hier
Zögern Sie nicht, mich zu kontaktieren unter