You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Multipeer Connectivity问题:发送邀请时对等端丢失

Multipeer Connectivity 连接异常问题:发送邀请后对等端断开并触发lostPeer

我正在用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 08:01:40