Kết nối đa ngang hàng là một giải pháp thay thế cho định dạng trao đổi dữ liệu thông thường. Thay vì trao đổi dữ liệu qua Wi-Fi hoặc Mạng di động thông qua một môi giới trung gian, thường là máy chủ phụ trợ, Kết nối đa ngang hàng cung cấp khả năng trao đổi thông tin giữa nhiều thiết bị gần nhau mà không cần trung gian.
iPhone và iPad sử dụng công nghệ Wi-Fi và Bluetooth, trong khi MacBook và Apple TV sử dụng Wi-Fi và Ethernet.
Từ đây, ưu và nhược điểm của công nghệ này sẽ xuất hiện ngay sau đó. Ưu điểm bao gồm tính phi tập trung và theo đó là khả năng trao đổi thông tin mà không cần trung gian.
Nhược điểm — chia sẻ bị giới hạn ở phạm vi phủ sóng Wi-Fi và Bluetooth cho iPhone và iPad hoặc Wi-Fi và Ethernet cho MacBook và Apple TV. Nói cách khác, việc trao đổi thông tin có thể được thực hiện ngay tại vị trí gần các thiết bị.
Việc tích hợp kết nối đa ngang hàng không phức tạp và bao gồm các bước sau:
Dự án cài đặt trước
Thiết lập khả năng hiển thị cho các thiết bị khác
Quét các thiết bị có thể nhìn thấy trong phạm vi
Tạo một cặp thiết bị để trao đổi dữ liệu
Trao đổi dữ liệu
Chúng ta hãy xem xét kỹ hơn từng bước trên.
Ở giai đoạn này, dự án phải được chuẩn bị để triển khai Kết nối đa cấp. Để thực hiện việc này, bạn cần có thêm quyền từ người dùng để có thể quét:
Info.plist
với mô tả về mục đích sử dụng;Info.plist
cũng cần được bổ sung thêm các dòng sau:
<key>NSBonjourServices</key> <array> <string>_nearby-devices._tcp</string> <string>_nearby-devices._upd</string> </array>
Điều quan trọng cần lưu ý là chuỗi con nearby-devices
được sử dụng làm ví dụ trong ngữ cảnh này. Trong dự án của bạn, khóa này phải đáp ứng các yêu cầu sau:
Các ký tự hợp lệ dài từ 1–15 ký tự bao gồm các chữ cái thường ASCII, số và dấu gạch nối, chứa ít nhất một chữ cái và không có dấu gạch nối liền kề.
Bạn có thể đọc thêm về các yêu cầu__ tại đây__ .
Đối với giao thức truyền thông, ví dụ sử dụng tcp
và upd
(giao thức đáng tin cậy hơn và kém tin cậy hơn). Nếu bạn không biết mình cần giao thức nào, bạn nên nhập cả hai.
Tổ chức khả năng hiển thị thiết bị cho kết nối nhiều đối tác được triển khai bởi MCNearbyServiceAdvertiser
. Hãy tạo một lớp chịu trách nhiệm phát hiện, hiển thị và chia sẻ thông tin giữa các thiết bị.
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 ) } }
Cốt lõi của multipeer là MCSession
, cho phép bạn kết nối và trao đổi dữ liệu giữa các thiết bị.
serviceType
là khóa được đề cập ở trên, được thêm vào tệp Info.plist
cùng với các giao thức trao đổi.
Thuộc tính isAdvertised
sẽ cho phép bạn chuyển đổi chế độ hiển thị của thiết bị bằng cách sử dụng Toggle
.
Quét khả năng hiển thị thiết bị cho kết nối nhiều đối tác được thực hiện bởi 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 }
Danh sách tất cả các thiết bị có thể nhìn thấy sẽ được lưu trữ trong peers
. Các phương thức ủy nhiệm MCNearbyServiceBrowser
sẽ thêm hoặc xóa MCPeerID
khi tìm thấy hoặc mất đối tác.
Phương thức startBrowsing
và finishBrowsing
sẽ được sử dụng để bắt đầu phát hiện các thiết bị có thể nhìn thấy khi màn hình xuất hiện hoặc dừng tìm kiếm sau khi màn hình biến mất.
View
sau đây sẽ được sử dụng làm 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) } } } } }
Khả năng hiển thị của thiết bị sẽ được bật/tắt bằng Toggle
.
Do đó, ở giai đoạn này, việc phát hiện và hiển thị thiết bị sẽ hoạt động chính xác.
Phương thức ủy nhiệm MCNearbyServiceAdvertiserdidReceiveInvitationFromPeer
chịu trách nhiệm gửi lời mời giữa một cặp thiết bị. Cả hai thiết bị đều phải có khả năng xử lý yêu cầu này.
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 }
Khi selectedPeer
được thiết lập, phương thức connect sẽ kích hoạt. Nếu peer
này nằm trong danh sách peers
hiện có, nó sẽ được thêm vào mảng joinedPeer
. Trong tương lai, thuộc tính này sẽ được xử lý bởi UI.
Nếu không có thiết bị này trong phiên, browser
sẽ mời thiết bị này tạo một cặp.
Sau đó, phương thức didReceiveInvitationFromPeer
sẽ được xử lý cho thiết bị được mời. Trong trường hợp của chúng tôi, sau khi didReceiveInvitationFromPeer
bắt đầu, một permissionRequest
được tạo với lệnh gọi lại bị trì hoãn, sẽ được hiển thị dưới dạng cảnh báo trên thiết bị được mời:
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) }) ) }) ... } } }
Trong trường hợp phê duyệt, didReceiveInvitationFromPeer
sẽ trả về thiết bị gửi lời mời, quyền và phiên nếu quyền được cấp thành công.
Kết quả là sau khi chấp nhận lời mời thành công, một cặp sẽ được tạo:
Sau khi tạo cặp, MCSession
chịu trách nhiệm trao đổi dữ liệu:
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) } }
Phương thức func send(_ data: Data, toPeers peerIDs: [MCPeerID], with mode: MCSessionSendDataMode) throws
giúp gửi dữ liệu giữa các đối tác.
Phương thức ủy nhiệm func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID)
được kích hoạt trên thiết bị đã nhận được tin nhắn.
Ngoài ra, một nhà xuất bản trung gian messagePublisher
được sử dụng để nhận tin nhắn, vì các phương thức ủy nhiệm MCSession
được kích hoạt trong DispatchQueue global()
.
Có thể tìm thấy thêm thông tin chi tiết về nguyên mẫu tích hợp Multipeer Connectivity trong
Đừng ngần ngại liên hệ với tôi qua