paint-brush
Domine a conectividade multipeer do iOS e compartilhe dados entre vários dispositivos sem acesso à Internetpor@bugorbn
251 leituras

Domine a conectividade multipeer do iOS e compartilhe dados entre vários dispositivos sem acesso à Internet

por Boris Bugor12m2024/08/19
Read on Terminal Reader

Muito longo; Para ler

A conectividade Multipeer permite a troca direta de dados entre dispositivos Apple usando Wi-Fi, Bluetooth e Ethernet, ignorando servidores tradicionais. O artigo descreve os benefícios, limitações e etapas para integrar essa tecnologia em seus projetos.
featured image - Domine a conectividade multipeer do iOS e compartilhe dados entre vários dispositivos sem acesso à Internet
Boris Bugor HackerNoon profile picture
0-item


Multipeer Connectivity é uma alternativa ao formato comum de troca de dados. Em vez de trocar dados via Wi-Fi ou Rede Celular por meio de um broker intermediário, que geralmente é um servidor backend, Multipeer Connectivity fornece a capacidade de trocar informações entre vários dispositivos próximos sem intermediários.


O iPhone e o iPad usam tecnologia Wi-Fi e Bluetooth, enquanto o MacBook e a Apple TV dependem de Wi-Fi e Ethernet.


A partir daqui, os prós e contras dessa tecnologia seguem imediatamente. As vantagens incluem descentralização e, consequentemente, a capacidade de trocar informações sem intermediários.


Desvantagens — o compartilhamento é limitado à cobertura Wi-Fi e Bluetooth para iPhone e iPad, ou Wi-Fi e Ethernet para MacBook e Apple TV. Em outras palavras, a troca de informações pode ser realizada nas imediações dos dispositivos.


A integração da conectividade multipeer não é complicada e consiste nas seguintes etapas:

  1. Predefinição do projeto

  2. Configurar visibilidade para outros dispositivos

  3. Procure por dispositivos visíveis dentro do alcance

  4. Criando um par de dispositivos para troca de dados

  5. Troca de dados


Vamos analisar mais detalhadamente cada uma das etapas acima.


1. Predefinição do projeto

Nesta fase, o projeto deve ser preparado para a implementação do Multipeer Connectivity. Para isso, você precisa obter permissões adicionais do usuário para poder escanear:

  • adicione Privacidade — Descrição de uso da rede local ao arquivo Info.plist com uma descrição da finalidade do uso;
  • Além disso, para a possibilidade de troca de informações, Info.plist também precisa ser complementado com as seguintes linhas:


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


É importante notar que a substring nearby-devices é usada como um exemplo neste contexto. No seu projeto, esta chave deve atender aos seguintes requisitos:

1–15 caracteres e caracteres válidos incluem letras minúsculas ASCII, números e o hífen, contendo pelo menos uma letra e nenhum hífen adjacente.

Você pode ler mais sobre os requisitos__ aqui __.

Quanto aos protocolos de comunicação, o exemplo usa tcp e upd (o mais confiável e o menos confiável). Se você não sabe qual protocolo precisa, você deve digitar ambos.

2. Configurar visibilidade para outros dispositivos

A organização da visibilidade do dispositivo para conexão multi-peer é implementada por MCNearbyServiceAdvertiser . Vamos criar uma classe que será responsável por detectar, exibir e compartilhar informações 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 ) } }


O núcleo do multipeer é uma MCSession , que permitirá que você se conecte e troque dados entre dispositivos.

O serviceType é a chave mencionada acima, que foi adicionada ao arquivo Info.plist junto com os protocolos de troca.

A propriedade isAdvertised permitirá que você alterne a visibilidade do dispositivo usando Toggle .

3. Procure por dispositivos visíveis dentro do alcance

A varredura de visibilidade do dispositivo para uma conexão multi-peer é realizada pelo 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 }


Uma lista de todos os dispositivos visíveis será armazenada em peers . Os métodos delegados MCNearbyServiceBrowser adicionarão ou removerão um MCPeerID quando um peer for encontrado ou perdido.


Os métodos startBrowsing e finishBrowsing serão usados para começar a descobrir dispositivos visíveis quando a tela aparecer ou parar a pesquisa depois que a tela desaparecer.


A seguinte View será usada como interface do usuário:


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


A visibilidade do dispositivo será ativada/desativada pelo Toggle .
Como resultado, nesta fase, a detecção e a exibição dos dispositivos devem funcionar corretamente.


4. Criação de um par de dispositivos para troca de dados

O método delegado MCNearbyServiceAdvertiserdidReceiveInvitationFromPeer é responsável por enviar um convite entre um par de dispositivos. Ambos devem ser capazes de manipular essa solicitação.


 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 }


Quando selectedPeer é definido, o método connect dispara. Se esse peer estiver na lista de peers existentes, ele será adicionado ao array joinedPeer . No futuro, essa propriedade será processada pela UI.


Na ausência deste par na sessão, o browser convidará este dispositivo a criar um par.


Depois disso, o método didReceiveInvitationFromPeer será processado para o dispositivo convidado. No nosso caso, após o início de didReceiveInvitationFromPeer , um permissionRequest é criado com um callback atrasado, que será mostrado como um alerta no dispositivo convidado:


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


No caso de uma aprovação, didReceiveInvitationFromPeer retornará o dispositivo que enviou o convite, a permissão e a sessão, se a permissão foi bem-sucedida.


Como resultado, após aceitar o convite com sucesso, um par será criado:


5. Troca de dados

Após criar um par, MCSession é responsável pela troca de dados:


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


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


O método delegado func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) é acionado no dispositivo que recebeu a mensagem.


Além disso, um publicador intermediário messagePublisher é usado para receber mensagens, já que os métodos delegados MCSession são disparados no DispatchQueue global() .


Mais detalhes sobre o protótipo de integração Multipeer Connectivity podem ser encontrados neste repositório . Como exemplo, essa tecnologia proporcionou a capacidade de trocar mensagens entre dispositivos.



Não hesite em contactar-me em Twitter se você tiver alguma dúvida. Além disso, você sempre pode me compre um café .