paint-brush
iOS 멀티피어 연결 마스터하고 인터넷 접속 없이도 여러 기기에서 데이터 공유~에 의해@bugorbn
251 판독값

iOS 멀티피어 연결 마스터하고 인터넷 접속 없이도 여러 기기에서 데이터 공유

~에 의해 Boris Bugor12m2024/08/19
Read on Terminal Reader

너무 오래; 읽다

Multipeer Connectivity는 기존 서버를 우회하여 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 및 이더넷으로 제한됩니다. 즉, 정보 교환은 장치 바로 근처에서 수행할 수 있습니다.


Multipeer Connectivity 통합은 복잡하지 않으며 다음 단계로 구성됩니다.

  1. 프로젝트 사전 설정

  2. 다른 장치에 대한 가시성 설정

  3. 범위 내에서 보이는 장치를 검색합니다.

  4. 데이터 교환을 위한 장치 쌍 생성

  5. 데이터 교환


위의 각 단계를 자세히 살펴보겠습니다.


1. 프로젝트 사전 설정

이 단계에서는 Multipeer Connectivity 구현을 위해 프로젝트를 준비해야 합니다. 이를 위해 사용자로부터 추가 권한을 얻어서 스캔할 수 있어야 합니다.

  • 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 대리자 메서드는 피어가 발견되거나 손실되면 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. 데이터 교환

쌍을 생성한 후 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 피어 간에 데이터를 전송하는 데 도움이 됩니다.


메시지를 수신한 장치에서 delegate 메서드 func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) 가 트리거됩니다.


또한, 중간 게시자 messagePublisher MCSession 대리자 메서드가 DispatchQueue global() 에서 실행되기 때문에 메시지를 수신하는 데 사용됩니다.


Multipeer Connectivity 통합 프로토타입에 대한 자세한 내용은 여기에서 확인할 수 있습니다. 저장소 . 예를 들어, 이 기술은 장치 간에 메시지를 교환하는 기능을 제공했습니다.



언제든지 저에게 연락주세요. 지저귀다 질문이 있으면 언제든지. 또한, 나한테 커피 한 잔 사줘 .