La connectivité multipeer est une alternative au format d'échange de données habituel. Au lieu d'échanger des données via Wi-Fi ou réseau cellulaire via un intermédiaire, qui est généralement un serveur back-end, la connectivité multipeer offre la possibilité d'échanger des informations entre plusieurs appareils à proximité sans intermédiaire.
L'iPhone et l'iPad utilisent les technologies Wi-Fi et Bluetooth, tandis que le MacBook et l'Apple TV s'appuient sur le Wi-Fi et l'Ethernet.
Les avantages et les inconvénients de cette technologie découlent immédiatement de cela. Parmi les avantages, on peut citer la décentralisation et, par conséquent, la possibilité d'échanger des informations sans intermédiaires.
Inconvénients : le partage est limité à la couverture Wi-Fi et Bluetooth pour iPhone et iPad, ou Wi-Fi et Ethernet pour MacBook et Apple TV. En d'autres termes, l'échange d'informations peut être effectué à proximité immédiate des appareils.
L'intégration de la connectivité multi-pairs n'est pas compliquée et comprend les étapes suivantes :
Préréglage du projet
Configurer la visibilité pour d’autres appareils
Rechercher les appareils visibles à portée
Création d'une paire d'appareils pour l'échange de données
Échange de données
Examinons de plus près chacune des étapes ci-dessus.
À ce stade, le projet doit être préparé pour la mise en œuvre de la connectivité multipeer. Pour ce faire, vous devez obtenir des autorisations supplémentaires de la part de l'utilisateur pour pouvoir scanner :
Info.plist
avec une description de l’objectif d’utilisation ;Info.plist
doit également être complété par les lignes suivantes :
<key>NSBonjourServices</key> <array> <string>_nearby-devices._tcp</string> <string>_nearby-devices._upd</string> </array>
Il est important de noter que la sous-chaîne nearby-devices
est utilisée comme exemple dans ce contexte. Dans votre projet, cette clé doit répondre aux exigences suivantes :
1 à 15 caractères de long et les caractères valides incluent les lettres minuscules ASCII, les chiffres et le trait d'union contenant au moins une lettre et aucun trait d'union adjacent.
Vous pouvez en savoir plus sur les exigences__ ici __.
En ce qui concerne les protocoles de communication, l'exemple utilise tcp
et upd
(le plus fiable et l'autre moins fiable). Si vous ne savez pas quel protocole vous avez besoin, vous devez saisir les deux.
L'organisation de la visibilité des appareils pour les connexions multi-homologues est implémentée par MCNearbyServiceAdvertiser
. Créons une classe qui sera responsable de la détection, de l'affichage et du partage des informations entre les appareils.
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 ) } }
Le cœur du multipeer est une MCSession
, qui vous permettra de vous connecter et d'échanger des données entre les appareils.
Le serviceType
est la clé mentionnée ci-dessus, qui a été ajoutée au fichier Info.plist
avec les protocoles d'échange.
La propriété isAdvertised
vous permettra de changer la visibilité de l'appareil à l'aide Toggle
.
L'analyse de la visibilité des appareils pour une connexion multi-homologues est effectuée par MCNearbyServiceBrowser
:
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 }
Une liste de tous les périphériques visibles sera stockée dans peers
. Les méthodes déléguées MCNearbyServiceBrowser
ajouteront ou supprimeront un MCPeerID
lorsqu'un pair est trouvé ou perdu.
Les méthodes startBrowsing
et finishBrowsing
seront utilisées pour démarrer la découverte des périphériques visibles lorsque l'écran apparaît, ou arrêter la recherche après la disparition de l'écran.
La View
suivante sera utilisée comme interface utilisateur :
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) } } } } }
La visibilité de l'appareil sera activée/désactivée par la Toggle
.
Par conséquent, à ce stade, la détection et l’affichage des appareils devraient fonctionner correctement.
La méthode déléguée MCNearbyServiceAdvertiserdidReceiveInvitationFromPeer
est responsable de l'envoi d'une invitation entre une paire d'appareils. Les deux doivent être capables de gérer cette demande.
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 }
Lorsque le selectedPeer
est défini, la méthode connect se déclenche. Si ce peer
figure dans la liste des peers
existants, il sera ajouté au tableau joinedPeer
. À l'avenir, cette propriété sera traitée par l'interface utilisateur.
En l’absence de ce pair dans la session, le browser
invitera cet appareil à créer une paire.
Après cela, la méthode didReceiveInvitationFromPeer
sera traitée pour l'appareil invité. Dans notre cas, après le démarrage de didReceiveInvitationFromPeer
, une permissionRequest
est créée avec un rappel différé, qui sera affiché sous forme d'alerte sur l'appareil invité :
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) }) ) }) ... } } }
En cas d'approbation, didReceiveInvitationFromPeer
renverra l'appareil envoyant l'invitation, l'autorisation et la session si l'autorisation a réussi.
En conséquence, après avoir accepté avec succès l'invitation, une paire sera créée :
Après avoir créé une paire, MCSession
est responsable de l'échange de données :
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) } }
La méthode func send(_ data: Data, toPeers peerIDs: [MCPeerID], with mode: MCSessionSendDataMode) throws
d'envoyer des données entre pairs.
La méthode déléguée func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID)
est déclenchée sur l'appareil qui a reçu le message.
De plus, un éditeur intermédiaire messagePublisher
est utilisé pour recevoir des messages, puisque les méthodes déléguées MCSession
se déclenchent dans DispatchQueue global()
.
Vous trouverez plus de détails sur le prototype d'intégration de connectivité multipeer dans ce
N'hésitez pas à me contacter sur