paint-brush
Domine la conectividad multipeer de iOS y comparta datos entre múltiples dispositivos sin acceso a Internetpor@bugorbn
235 lecturas

Domine la conectividad multipeer de iOS y comparta datos entre múltiples dispositivos sin acceso a Internet

por Boris Bugor12m2024/08/19
Read on Terminal Reader

Demasiado Largo; Para Leer

La conectividad multipeer permite el intercambio directo de datos entre dispositivos Apple mediante Wi-Fi, Bluetooth y Ethernet, sin necesidad de utilizar servidores tradicionales. En este artículo se describen los beneficios, las limitaciones y los pasos para integrar esta tecnología en sus proyectos.
featured image - Domine la conectividad multipeer de iOS y comparta datos entre múltiples dispositivos sin acceso a Internet
Boris Bugor HackerNoon profile picture
0-item


La conectividad multipeer es una alternativa al formato común de intercambio de datos. En lugar de intercambiar datos a través de Wi-Fi o red celular mediante un intermediario, que suele ser un servidor backend, la conectividad multipeer brinda la capacidad de intercambiar información entre múltiples dispositivos cercanos sin intermediarios.


El iPhone y el iPad utilizan tecnología Wi-Fi y Bluetooth, mientras que MacBook y Apple TV dependen de Wi-Fi y Ethernet.


De aquí se desprenden inmediatamente los pros y los contras de esta tecnología. Entre las ventajas se encuentra la descentralización y, en consecuencia, la posibilidad de intercambiar información sin intermediarios.


Desventajas: el intercambio de información se limita a la cobertura Wi-Fi y Bluetooth para iPhone y iPad, o Wi-Fi y Ethernet para MacBook y Apple TV. En otras palabras, el intercambio de información puede realizarse en las inmediaciones de los dispositivos.


La integración de la conectividad multipeer no es complicada y consta de los siguientes pasos:

  1. Proyecto preestablecido

  2. Configurar la visibilidad para otros dispositivos

  3. Escanear en busca de dispositivos visibles dentro del alcance

  4. Creación de un par de dispositivos para el intercambio de datos

  5. Intercambio de datos


Veamos más de cerca cada uno de los pasos anteriores.


1. Proyecto preestablecido

En esta etapa, el proyecto debe estar preparado para la implementación de la conectividad multipeer. Para ello, es necesario obtener permisos adicionales del usuario para poder escanear:

  • agregue Privacidad — Descripción del uso de la red local al archivo Info.plist con una descripción del propósito de uso;
  • Además, para la posibilidad de intercambio de información, Info.plist también debe complementarse con las siguientes líneas:


 <key>NSBonjourServices</key> <array> <string>_nearby-devices._tcp</string> <string>_nearby-devices._upd</string> </array>


Es importante tener en cuenta que la subcadena nearby-devices se utiliza como ejemplo en este contexto. En su proyecto, esta clave debe cumplir los siguientes requisitos:

Tiene una longitud de 1 a 15 caracteres y los caracteres válidos incluyen letras minúsculas ASCII, números y el guion, que contengan al menos una letra y ningún guion adyacente.

Puede leer más sobre los requisitos__ aquí__ .

En cuanto a los protocolos de comunicación, en el ejemplo se utilizan tcp y upd (uno más fiable y otro menos fiable). Si no sabes qué protocolo necesitas, debes introducir ambos.

2. Configurar la visibilidad para otros dispositivos

La organización de la visibilidad de los dispositivos para la conexión entre múltiples pares se implementa mediante MCNearbyServiceAdvertiser . Creemos una clase que será responsable de detectar, mostrar y compartir información entre dispositivos.


 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 ) } }


El núcleo del multipeer es una MCSession , que le permitirá conectarse e intercambiar datos entre dispositivos.

serviceType es la clave mencionada anteriormente, que se agregó al archivo Info.plist junto con los protocolos de intercambio.

La propiedad isAdvertised le permitirá cambiar la visibilidad del dispositivo usando Toggle .

3. Busque dispositivos visibles dentro del alcance

El escaneo de visibilidad del dispositivo para una conexión multi-peer lo realiza 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 }


Se almacenará una lista de todos los dispositivos visibles en peers . Los métodos delegados MCNearbyServiceBrowser agregarán o eliminarán un MCPeerID cuando se encuentre o se pierda un peer.


Los métodos startBrowsing y finishBrowsing se utilizarán para comenzar a descubrir dispositivos visibles cuando aparezca la pantalla o detener la búsqueda después de que la pantalla desaparezca.


La siguiente View se utilizará como interfaz de usuario:


 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 visibilidad del dispositivo se habilitará o deshabilitará mediante el Toggle .
Como resultado, en esta etapa, la detección y visualización de dispositivos deberían funcionar correctamente.


4. Creación de un par de dispositivos para el intercambio de datos

El método delegado MCNearbyServiceAdvertiserdidReceiveInvitationFromPeer es responsable de enviar una invitación entre un par de dispositivos. Ambos deben ser capaces de gestionar esta solicitud.


 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 }


Cuando se configura selectedPeer , se activa el método connect. Si este peer está en la lista de peers existentes, se agregará a la matriz joinedPeer . En el futuro, la UI procesará esta propiedad.


En ausencia de este par en la sesión, el browser invitará a este dispositivo a crear un par.


Después de eso, se procesará el método didReceiveInvitationFromPeer para el dispositivo invitado. En nuestro caso, después del inicio de didReceiveInvitationFromPeer , se crea un permissionRequest con una devolución de llamada retrasada, que se mostrará como una alerta en el dispositivo invitado:


 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 el caso de una aprobación, didReceiveInvitationFromPeer devolverá el dispositivo que envió la invitación, el permiso y la sesión si el permiso fue exitoso.


Como resultado, después de aceptar con éxito la invitación, se creará un par:


5. Intercambio de datos

Después de crear un par, MCSession es responsable del intercambio de datos:


 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) } }


El método func send(_ data: Data, toPeers peerIDs: [MCPeerID], with mode: MCSessionSendDataMode) throws ayuda a enviar datos entre pares.


El método delegado func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) se activa en el dispositivo que recibió el mensaje.


Además, se utiliza un mensaje de publicador intermedio messagePublisher para recibir mensajes, ya que los métodos delegados MCSession se activan en el DispatchQueue global() .


Se pueden encontrar más detalles sobre el prototipo de integración de conectividad multipeer en este repositorio Como ejemplo, esta tecnología ha proporcionado la capacidad de intercambiar mensajes entre dispositivos.



No dudes en contactarme en Gorjeo Si tienes alguna pregunta, siempre puedes cómprame un café .