Multipeer Connectivity에 대해
- SwiftUI
- Swift
- MultipeerConnectivity

Multipeer Connectivity | Apple Developer Documentation
MultipeerConnectivity 는 주변에 있는 Apple 기기끼리 인터넷 연결 없이도 직접 통신(P2P)를 할 수 있게 해주는 프레임워크이다.
→ SwiftUI에선 이걸 자체적으로 지원하지는 않고 ViewModel이라던지 별도의 객체로 감싸서 사용한다고 한다.
Wi-Fi와 P2P Wi-Fi, Bluetooth를 사용하므로 같은 공간에 있는 기기들끼리 채팅, 게임, 파일 전송 같은 로컬 멀티 플레이 또는 공유 기능을 만들 때 사용한다.
💁♂️ 짚고 넘어가기
- iOS에서는 프레임워크 전송을 위해
Wi-Fi,P2P Wi-Fi,Bluetooth를 사용한다. - macOS 및 tvOS에서는
Wi-Fi,P2P Wi-Fi,이더넷을 사용한다.
→ 두 기기가 같은 Wi-Fi 네트워크에 있으면 Wi-Fi를, 그렇지 않으면 P2P Wi-Fi 또는 Bluetooth를 자동으로 선택한다.
→ 즉, 로컬 네트워크를 사용하기 때문에 Info.plist에 NSLocalNetworkUsageDescription 를 선언해야한다.
→ 또한, 이때 내부적으로 Bonjour 프로토콜을 사용하기 때문에 NSBonjourServices 도 선언해야한다.
🤔 _Bonjour 프로토콜이란 뭐고 왜 사용하는걸까?_
Bonjour은 로컬 네트워크에서 “이 포트로 어떤 서비스가 열려있어요!(ex: 해당 포트에 채팅 서버가 열려있네요?)”를 알기 위한 Apple의 zeroconf 서비스 디스커버리 프로토콜이다.
https://en.wikipedia.org/wiki/Bonjour_(software)
- Bonjour는 mDNS(멀티캐스트 DNS)와 DNS-SD(DND 서비스 디스커버리)를 이용해 같은 네트워크에 있는 장치들이 서로의 서비스를
이름 기반으로 찾게 해준다. - 이러한 특성으로 인해 사용자는 IP나 포트 번호를 직접 설정하지 않고도
_http._tcp.local.처럼 서비스 타입과 이름만으로 접근할 수 있다.
→ 그렇다면 MultipeerConnectivity 과 Bonjour는 무슨 관계냐?
→ MultipeerConnectivity는 주변 기기 서비스 검색 을 위해 내부적으로 Bonjour 위에 레이어를 올린 프레임워크이기 때문에 실제 피어 검색과 광고가 Bonjour 매커니즘을 통해 이뤄진다!
→ 따라서 Info.plist에 NSBonjourServices 를 명시하여 어떤 Bongjour 서비스 타입을 탐색하고 광고할 것인지 명시해야한다.
🪛 구성 요소
📌 MCPeerID
MCPeerID | Apple Developer Documentation
MCPeerID는 각 기기(기기에서 실행 중인 앱 인스턴스)를 고유하기 식별하는 ID, 혹은 세션 내에서 고유하게 식별하는 클래스를 의미한다.
보통은 사용자의 기기 이름을 사용한다고 한다.
private let peerID = MCPeerID(displayName: UIDevice.current.name)
📌 MCSession
MCSession | Apple Developer Documentation
연결된 피어들 간의 실제 데이터 송수신을 담당하는 세션 객체이다.
MCSession을 통해 메세지나 데이터 스트림, 리소스를 전송할 수 있다.
세션은 연결 상태를 MCSessionDelegate를 통해 .notConnected, .connecting , .connected 상태로 알려준다.
private let session: MCsession
override init() {
session = MCSession(peer: myPeerId,
securityIndentity: nil,
encryptionPreference: .none)
session.delegate = self
}
📌 MCNearbyServiceAdvertiser
MCNearbyServiceAdvertiser | Apple Developer Documentation
앱이 지정된 유형의 세션에 참여할 의향이 있음을 주변 피어에게 알리는 역할을 수행한다.
Advertiser Object는 단일 로컬 피어를 사용하여 장치와 해당 사용자를 식별하는 정보를 근처 다른 장치에 제공한다.
→ 쉽게 말하면 나 이런 서비스 타입으로 통신할 수 있어요! 하고 광고하는 것이다.
→ 더 쉽게 말하면 다른 기기들에게 자신의 존재를 광고한다. (애초에 클래스 이름에 광고가 붙어있네)
private let serviceAdvertiser: MCNearbyServiceAdvertiser
serviceAdvertiser = MCNearbyServiceAdvertiser(peer: peerID,
discoveryInfo: nil,
serviceType: serviceType)
serviceAdvertiser.delegate = self
serviceAdvertiser.startAdvertisingPeer()
📌 MCNearbyServiceBrowser
MCNearbyServiceBrowser | Apple Developer Documentation
앱이 특정 유형의 세션을 지원하는 앱이 있는 주변 장치를 검색한다.
Advertiser는 자기 자신을 알리는 역할이라면 Browser는 자기 자신을 알리는 피어를 찾고 연결 초대를 보낸다.
→ 즉, 근처의 다른 피어(기기)들을 찾는 역할이다.
private let serviceBrowser: MCNearbyServiceBrowser
serviceBrowser = MCNearbyServiceBrowser(peer: peerID, serviceType: serviceType)
serviceBrowser.delegate = self
serviceBrowser.startBrowsingForPeers()
📌 MCBrowserViewController, MCAdvertiserAssistant
MCBrowserViewController | Apple Developer Documentation
MCAdvertiserAssistant | Apple Developer Documentation
UIKit 기반 기본 UI를 제공하는 도우미로 사용자가 직접 피어를 선택하거나 초대를 처리할 수 있게 해준다.
MCBrowserViewController의 경우 사용자가 세션에 추가할 주변 피어를 선택할 수 있는 표준 인터페이스를 제공해준다.MCAdvertiserAssistant의 경우MCBrowserViewController와 같이 사용자가 초대를 수락할 수 있는 표준 사용자 인터페이스를 제공해주지만 고유한 사용자 인터페이스를 사용하거나 표시되는 초대를 개발자
🙌 쉽게 이해하는 작동 순서
💡 1.
MCPeerID를 통해 장치를 고유하게 식별한다.
MCNearbyServiceAdvertiser를 사용하여 내 서비스를 주변에 알린다.
MCNearbyServiceBrowser를 통해 네트워크에서 근처 서비스를 찾는다.그 후
MCSession을 통해 연결된 모든 장치(피어)를 관리하고 메세지 송수신을 허용한다.
MCPeerID를 통해 장치를 고유하게 식별한다.MCNearbyServiceAdvertiser를 사용하여 내 서비스를 주변에 알린다.MCNearbyServiceBrowser를 통해 네트워크에서 근처 서비스를 찾는다.- 그 후
MCSession을 통해 연결된 모든 장치(피어)를 관리하고 메세지 송수신을 허용한다.
1단계 (검색 단계)
- A기기는
advertiser.startAdvertisingPeer()를 자신을 근처에 알린다. - B기기는
browser.startBrowsingForPeers()로 근처 기기를 검색한다. - B기기가 A기기를 발견하면
foundPeerDelegate를 호출한다.
2단계 (연결 요청)
- B기기에서
browser.invitePeer(A기기, to: session)으로 초대를 전송한다. - A기기에서
didReceiveInitationFromPeerDelegate를 호출한다.
3단계 (연결 승인 및 거절)
- A기기에서
invitationHandler(true, session)을 호출하면 연결이 시작된다. - 양쪽 기기에서
session:peer:didChange:delegate로 연결 상태에 따른 변화를 감지한다.
❕ 주의사항 : 각 단계마다 실패 가능성이 있으며 이를 모두 처리해야한다!!!
→ 대표적으로 발견된 기기가 사라질 경우 lostPeer delegate를 구현하여 따로 처리를 해야한다.
대표적인 연결 단계는 아래와 같다.
func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
DispatchQueue.main.async {
switch state {
case .notConnected:
print("연결 해제: \(peerID.displayName)")
// 실패한 연결 시도인지, 정상적인 연결 해제인지 구분 필요
case .connecting:
print("연결 시도 중: \(peerID.displayName)")
// 사용자에게 "연결 중" 표시
case .connected:
print("연결 완료: \(peerID.displayName)")
// 연결 성공 시 대기 중인 데이터 전송 시작
}
}
}
❕주의사항
UIDevice.current.name 등으로 몽이의 iPhone 과 같이 사용자 지정 기기명을 그대로 가져올 수 없다.
iOS 16+ 부터는 기본값이 바뀌었으며 사용자 지정 기기명을 가져오기 위해선 Apple의 별도 entitlement 승인이 필요하다…ㅠ
→ 무슨 의미냐면 iOS 16+ 부터 UIDevice.name은 “사용자 이름이 붙은 기기의 이름”이 아닌 단순히 모델명(iPhone이나 iPad)을 반환하는 API로 바뀌었다.
→ 관련 자료 : https://developer.apple.com/videos/play/wwdc2022/10096/
→ 따라서 “사용자가 설정한 기기의 이름”을 얻기 위해선 com.apple.developer.device-information.user-assigned-device-name entitlement가 필요하다.
- 다만, 디바이스의 이름이 앱의 핵심 기능에 반드시 필요한 경우에만 승인 및 신청이 가능하며 거절될 가능성이 크고, 심사 기간이 불규칙적이니… 꼭 고려하자..
https://developer.apple.com/contact/request/user-assigned-device-name/