Multipeer Connectivity问题:发送邀请时对等端丢失
我正在用Multipeer Connectivity框架实现两台iOS设备间的连接,目前已能发现附近设备并加入数组。需求是让用户查看所有附近设备,点击任意一个发起连接——比如发现PeerA和PeerB,点击PeerA就发送邀请,全程用自定义UI。但现在的问题是:向任意对等端发送邀请时,该对等端会直接断开,同时触发lostPeer方法:
func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) { print("Peer lost \(peerID.displayName)") if let p = self.availablePeers[peerID.displayName] as? CustomPeer{ self.availablePeers[peerID.displayName] = nil print("Remove from list") } print("Lost Peer") }
以下是完整的Connection.swift代码:
// Connection.swift // Prevue // // Created by Prevue on 29/08/2022. // import Foundation import MultipeerConnectivity class CustomPeer{ var info: [String: Any]? var peerID: MCPeerID! } class Connection: NSObject{ static let ServiceType = AppStrings.ConnectionServiceType var peerID: MCPeerID! var session: MCSession! var advertiser: ServiceAdvertiser! var browser: BrowserService! var availablePeers = [String: CustomPeer]() var didReceiveFrame: ((UIImage)-> Void)? // just for testing. func startDiscoveryAndAdvertising(with displayName: String){ peerID = MCPeerID(displayName: displayName) var info = ["name": displayName, "id": displayName + "_" + String(Int.random(in: 1..<400)), "email": displayName + "@gmail.com"] self.advertiser = ServiceAdvertiser(peerID: peerID, serviceType: "Prevue", discoverInfo: info, delegate: self) // advertiser self.session = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .none) self.browser = BrowserService(peerID: peerID, serviceType: "Prevue", delegate: self) advertiser.advertiser.startAdvertisingPeer() browser.browser.startBrowsingForPeers() } func startDiscoveryAndAdvertising(with user: Profile){ peerID = MCPeerID(displayName: "\(user.id)") var info = ["name": user.name, "id": String(user.id), "email": user.email] self.advertiser = ServiceAdvertiser(peerID: peerID, serviceType: "Prevue", discoverInfo: info, delegate: self) // advertiser self.session = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .none) self.browser = BrowserService(peerID: peerID, serviceType: AppStrings.ConnectionServiceType, delegate: self) advertiser.advertiser.startAdvertisingPeer() browser.browser.startBrowsingForPeers() } func sendData(data: Data){ guard let session = self.session else{ return } do{ try session.send(data, toPeers: self.session.connectedPeers, with: .reliable) } catch let error { } } func sendInvite(to user: Profile){ guard let customPeer = self.availablePeers["\(user.id)"] as? CustomPeer, let peerID = customPeer.peerID else{ return } guard let user = UserDefaultsService.getProfileDataFromDefaults()?.profile else{ return } let d = ["user_id": user.id, "name": user.name, "persona": currentPersona.rawValue] as [String : Any] self.browser.browser.invitePeer(peerID, to: self.session, withContext: nil, timeout: TimeIntervals.Interval0) } func isUserReachable(user: Profile)-> CustomPeer?{ print("Available peers ") print(self.availablePeers) if let p = self.availablePeers["\(user.id)"] as? CustomPeer{ return p } return nil } } extension Connection: MCSessionDelegate{ func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) { switch state { case .connected: print ("connected \(peerID)") case .connecting: print ("connecting \(peerID)") case .notConnected: print ("not connected \(peerID)") default: print("unknown status for \(peerID)") } } func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { if let dict = NSKeyedUnarchiver.unarchiveObject(with: data) as? [String:Any] { // first check for captured photo if let photoData = dict["capturedPhoto"] as? Data, let image = UIImage(data: photoData) { } else{ guard let imageData = dict["image"] as? Data else { return } guard let image = UIImage.init(data: imageData, scale: 2.0) else { return } if self.didReceiveFrame != nil{ self.didReceiveFrame!(image) } print(peerID.displayName) } } else{ let commandString = String.init(data: data, encoding: String.Encoding.utf8) if commandString == "raiseFramerate" { } else if (commandString == "lowerFramerate") { } } } func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { } func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { } func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) { } func browserViewControllerDidFinish(_ browserViewController: MCBrowserViewController) { } func browserViewControllerWasCancelled(_ browserViewController: MCBrowserViewController) { } } //Browser delegate extension Connection: MCNearbyServiceBrowserDelegate{ func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) { print("Peer available \(peerID.displayName)") print(info) let p = MCPeerID(displayName: peerID.displayName) var customPeer = CustomPeer() customPeer.info = info customPeer.peerID = p self.availablePeers[p.displayName] = customPeer print("Found Peer \(p.displayName)") } func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) { print("Peer lost \(peerID.displayName)") if let p = self.availablePeers[peerID.displayName] as? CustomPeer{ self.availablePeers[peerID.displayName] = nil print("Remove from list") } print("Lost Peer") } } extension Connection: MCNearbyServiceAdvertiserDelegate{ func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) { // when a peer sends an invitation to connect. We show the custom dialogue here if let d = context{ do{ let json = try JSONSerialization.jsonObject(with: d) as? [String: Any] print(json) print("Someone wants to connect") } catch let error{ print(error.localizedDescription) } } } func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didNotStartAdvertisingPeer error: Error) { NSLog("%@", "didNotStartAdvertisingPeer: \(error)") } }
问题排查与修复方案
1. 邀请超时时间设置错误
在sendInvite(to:)方法中,你设置了timeout: TimeIntervals.Interval0,超时时间为0意味着邀请会立即过期,对方还没来得及处理邀请就被判定为超时,进而触发lostPeer。
修复:设置合理的超时时间,比如30秒:
self.browser.browser.invitePeer(peerID, to: self.session, withContext: nil, timeout: 30)
2. 未处理邀请回调
在advertiser(_:didReceiveInvitationFromPeer:withContext:invitationHandler:)方法中,你没有调用invitationHandler来响应邀请。如果不调用这个闭包,发起方会因为对方无响应而认为连接失败,导致对等端被标记为丢失。
修复:添加邀请处理逻辑,比如弹出自定义弹窗让用户选择接受或拒绝:
func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) { // 替换为你的自定义UI弹窗逻辑 let alert = UIAlertController(title: "连接邀请", message: "\(peerID.displayName) 请求连接", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "接受", style: .default) { _ in invitationHandler(true, self.session) }) alert.addAction(UIAlertAction(title: "拒绝", style: .cancel) { _ in invitationHandler(false, nil) }) // 主线程弹出弹窗 DispatchQueue.main.async { UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true) } if let d = context{ do{ let json = try JSONSerialization.jsonObject(with: d) as? [String: Any] print(json) print("Someone wants to connect") } catch let error{ print(error.localizedDescription) } } }
3. 重复创建MCPeerID
在foundPeer方法中,你基于已发现的peerID.displayName重新创建了一个MCPeerID对象,虽然displayName相同,但这是一个新对象,可能导致后续匹配或连接时出现识别问题。直接使用回调传入的peerID即可。
修复:
func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) { print("Peer available \(peerID.displayName)") print(info) let customPeer = CustomPeer() customPeer.info = info customPeer.peerID = peerID // 直接使用传入的peerID self.availablePeers[peerID.displayName] = customPeer print("Found Peer \(peerID.displayName)") }
4. ServiceType不一致
在startDiscoveryAndAdvertising(with:)的两个重载方法中,一个用了硬编码的"Prevue"作为ServiceType,另一个用了AppStrings.ConnectionServiceType。如果两者值不一致,会导致设备无法正确发现彼此,或者连接时出现异常。
修复:统一使用类常量Connection.ServiceType:
self.advertiser = ServiceAdvertiser(peerID: peerID, serviceType: Connection.ServiceType, discoverInfo: info, delegate: self) self.browser = BrowserService(peerID: peerID, serviceType: Connection.ServiceType, delegate: self)
内容的提问来源于stack exchange,提问作者Tech Labs

