paint-brush
iOS のマルチピア接続をマスターし、インターネットにアクセスせずに複数のデバイス間でデータを共有する@bugorbn
235 測定値

iOS のマルチピア接続をマスターし、インターネットにアクセスせずに複数のデバイス間でデータを共有する

Boris Bugor12m2024/08/19
Read on Terminal Reader

長すぎる; 読むには

マルチピア接続により、従来のサーバーを経由せずに、Wi-Fi、Bluetooth、イーサネットを使用して Apple デバイス間で直接データを交換できます。この記事では、このテクノロジーをプロジェクトに統合する利点、制限、手順について説明します。
featured image - iOS のマルチピア接続をマスターし、インターネットにアクセスせずに複数のデバイス間でデータを共有する
Boris Bugor HackerNoon profile picture
0-item


マルチピア接続は、一般的なデータ交換形式の代替手段です。中間ブローカー (通常はバックエンド サーバー) を介して Wi-Fi またはセルラー ネットワーク経由でデータを交換する代わりに、マルチピア接続では、仲介者なしで複数の近くのデバイス間で情報を交換できます。


iPhoneと iPad は Wi-Fi と Bluetooth テクノロジーを使用しますが、MacBook と Apple TV は Wi-Fi とイーサネットに依存します。


ここから、このテクノロジーの長所と短所がすぐにわかります。利点としては、分散化と、それに伴う仲介者なしで情報を交換できることが挙げられます。


デメリット - 共有は、iPhone と iPad の場合は Wi-Fi と Bluetooth の範囲内、MacBook と Apple TV の場合は Wi-Fi と Ethernet の範囲内に制限されます。つまり、情報の交換はデバイスのすぐ近くで行うことができます。


マルチピア接続の統合は複雑ではなく、次の手順で構成されます。

  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 小文字、数字、ハイフンが含まれ、少なくとも 1 つの文字が含まれ、隣接するハイフンは含まれません。

要件の詳細については、 ここ__でご覧いただけます__。

通信プロトコルに関しては、例では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 デリゲート メソッドはMCNearbyServiceBrowserピアが見つかった場合や失われた場合にMCPeerID追加または削除します。


startBrowsingfinishBrowsingメソッドは、画面が表示されたときに表示されているデバイスの検出を開始したり、画面が消えた後に検索を停止したりするために使用されます。


UI として次のViewが使用されます。


 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.データ交換

ペアを作成した後、 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使用してメッセージが受信されます。


マルチピア接続統合プロトタイプの詳細については、こちらをご覧ください。リポジトリたとえば、この技術により、デバイス間でメッセージを交換する機能が提供されました。



お気軽にご連絡くださいツイッターご質問があればいつでもご連絡ください。コーヒーを買って