paint-brush
掌握 iOS 多点连接并在无需互联网访问的情况下在多个设备之间共享数据 经过@bugorbn
242 讀數

掌握 iOS 多点连接并在无需互联网访问的情况下在多个设备之间共享数据

经过 Boris Bugor12m2024/08/19
Read on Terminal Reader

太長; 讀書

多点连接允许使用 Wi-Fi、蓝牙和以太网在 Apple 设备之间直接交换数据,绕过传统服务器。本文概述了将此技术集成到您的项目中的优势、局限性和步骤。
featured image - 掌握 iOS 多点连接并在无需互联网访问的情况下在多个设备之间共享数据
Boris Bugor HackerNoon profile picture
0-item


多点连接是通用数据交换格式的替代方案。多点连接无需通过中间代理(通常是后端服务器)通过 Wi-Fi 或蜂窝网络交换数据,而是能够在没有中间人的情况下在多个附近设备之间交换信息。


iPhone和 iPad 使用 Wi-Fi 和蓝牙技术,而 MacBook 和 Apple TV 依赖 Wi-Fi 和以太网。


从这里开始,这项技术的利弊就显而易见了。其优点包括去中心化,以及无需中介即可交换信息的能力。


缺点——共享仅限于 iPhone 和 iPad 的 Wi-Fi 和蓝牙覆盖范围,或 MacBook 和 Apple TV 的 Wi-Fi 和以太网覆盖范围。换句话说,信息交换可以在设备的附近进行。


多点连接的集成并不复杂,包括以下步骤:

  1. 项目预设

  2. 设置其他设备的可见性

  3. 扫描范围内的可见设备

  4. 创建一对用于数据交换的设备

  5. 数据交换


让我们仔细看看上述每个步骤。


1.项目预设

在此阶段,项目必须为实现多点连接做好准备。为此,您需要从用户那里获得额外的权限才能扫描:

  • Info.plist文件中添加隐私-本地网络使用说明,并说明使用目的;
  • 此外,为了实现信息交换的可能性, Info.plist还需要补充如下几行:


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


需要注意的是,此处以nearby-devices子字符串为例。在您的项目中,此键必须满足以下要求:

长度为 1-15 个字符,有效字符包括 ASCII 小写字母、数字和连字符,至少包含一个字母且不包含相邻的连字符。

您可以阅读有关要求的更多信息。

至于通信协议,示例中使用了tcpupd (可靠性较高和可靠性较低)。如果您不知道需要哪种协议,则应同时输入两者。

2.设置其他设备的可见性

多对等连接的设备可见性组织由MCNearbyServiceAdvertiser实现。让我们创建一个负责检测、显示和在设备之间共享信息的类。


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


多点连接的核心是MCSession ,它允许您在设备之间连接和交换数据。

serviceType就是上面提到的键,它与交换协议一起添加到Info.plist文件中。

isAdvertised属性允许您使用Toggle切换设备的可见性。

3.扫描范围内可见设备

多对等连接的设备可见性扫描由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 }


所有可见设备的列表将存储在peers中。当找到或丢失对等设备时, MCNearbyServiceBrowser委托方法将添加或删除MCPeerID


startBrowsingfinishBrowsing方法将用于在屏幕出现时开始发现可见设备,或在屏幕消失后停止搜索。


下列View将用作 UI:


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


设备可见性可以通过Toggle启用/禁用。
因此,现阶段设备的检测和显示应该可以正常工作。


4. 创建一对用于数据交换的设备

委托方法MCNearbyServiceAdvertiserdidReceiveInvitationFromPeer负责在两个设备之间发送邀请。两个设备都必须能够处理此请求。


 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 }


设置selectedPeer后,将触发 connect 方法。如果此peer位于现有peers列表中,它将被添加到joinedPeer数组中。将来,此属性将由 UI 处理。


当会话中没有该对等体时, browser将邀请该设备创建一对。


之后,将为受邀设备处理didReceiveInvitationFromPeer方法。在我们的例子中,在didReceiveInvitationFromPeer启动后,将创建一个具有延迟回调的permissionRequest ,该回调将作为警报显示在受邀设备上:


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


在批准的情况下,如果许可成功, didReceiveInvitationFromPeer将返回发送邀请、许可和会话的设备。


因此,成功接受邀请后,将创建一对:


5.数据交换

创建完pair之后, MCSession负责数据的交换:


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


方法func send(_ data: Data, toPeers peerIDs: [MCPeerID], with mode: MCSessionSendDataMode) throws有助于在对等体之间发送数据。


在收到消息的设备上触发委托方法func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID)


另外,由于MCSession委托方法在DispatchQueue global()中触发,因此使用中间发布者messagePublisher来接收消息。


有关多点连接集成原型的更多详细信息,请参阅此处举个例子,这项技术提供了设备之间交换消息的能力。



请随时联系我如果您有任何疑问。此外,您还可以随时


바카라사이트 바카라사이트 온라인바카라