英文:
Not being able to keep connection stable and send data across devices using MultipeerConnectivity
问题
我正在学习MultipeerConnectivity
框架的基础知识。我尝试在两个模拟器之间发送一些数据,所以我设置了一个小项目,并实现了MCBrowserViewControllerDelegate
、MCNearbyServiceAdvertiserDelegate
和MCSessionDelegate
协议。我添加了三个按钮,每个按钮都有一个相应的操作 - 名字应该相当不言自明:
advertiseButton
,关联了一个advertise()
方法;joinButton
,关联了一个join()
方法;sendDataButton
,关联了一个sendData()
方法。
使用我的代码,我实际上能够连接两个模拟器设备,因为我在控制台中打印出了"Connected"的输出,然而,在连接后的几秒钟内,我在控制台中记录了一堆错误:
[GCKSession] Not in connected state, so giving up for participant [1935E9EC] on channel [0].
[GCKSession] Not in connected state, so giving up for participant [1935E9EC] on channel [1].
[GCKSession] Not in connected state, so giving up for participant [1935E9EC] on channel [2].
[GCKSession] Not in connected state, so giving up for participant [1935E9EC] on channel [3].
我可能错了,但我猜这意味着连接丢失了。
这种情况每次都发生,所以可能是我代码中的某些实现出了问题。
我的第一个问题是:为什么会发生这种情况,我应该如何修复它?
此外,如果我在一个模拟器上点击sendData
按钮,另一个模拟器设备上不会收到任何数据,MCSessionDelegate
的didReceive data
回调从未被调用。我几乎可以确定这是由于上面的日志引起的,但即使在连接后按下按钮之前的短暂时间内也会发生这种情况。
所以,基本上,在我的代码中我做错了什么?
我正在使用XCode 14.3,在模拟器iPhone 14和iPhone 14 Pro上进行测试。
编辑: 刚在真实设备上进行了测试,出现了相同的问题。
这是完整的示例项目,只需复制粘贴它,如果需要,在Info.plist
中添加隐私设置,然后构建和运行:
import UIKit
import MultipeerConnectivity
final class ViewController: UIViewController {
// MARK: - Connectivity
private var peerID: MCPeerID!
private var session: MCSession!
private var nearbyServiceAdvertiser: MCNearbyServiceAdvertiser!
// MARK: - Buttons
private lazy var advertiseButton: UIButton = {
let button = UIButton()
button.addTarget(self, action: #selector(advertise), for: .touchUpInside)
button.setTitle("Advertise", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.titleLabel?.font = UIFont(name: "Arial", size: 24)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private lazy var joinButton: UIButton = {
let button = UIButton()
button.addTarget(self, action: #selector(join), for: .touchUpInside)
button.setTitle("Join", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.titleLabel?.font = UIFont(name: "Arial", size: 24)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private lazy var sendDataButton: UIButton = {
let button = UIButton()
button.addTarget(self, action: #selector(sendData), for: .touchUpInside)
button.setTitle("Send Data", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.titleLabel?.font = UIFont(name: "Arial", size: 24)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(advertiseButton)
view.addSubview(joinButton)
view.addSubview(sendDataButton)
peerID = MCPeerID(displayName: UIDevice.current.name)
session = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .required)
session.delegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
advertiseButton.frame = CGRect(x: 0, y: 500, width: view.frame.size.width, height: 50)
joinButton.frame = CGRect(x: 0, y: 600, width: view.frame.size.width, height: 50)
sendDataButton.frame = CGRect(x: 0, y: 700, width: view.frame.size.width, height: 50)
}
// MARK: - Selectors
@objc private func advertise() {
nearbyServiceAdvertiser = MCNearbyServiceAdvertiser(peer: peerID, discoveryInfo: nil, serviceType: "test")
nearbyServiceAdvertiser.delegate = self
nearbyServiceAdvertiser.startAdvertisingPeer()
}
@objc private func join() {
let browser = MCBrowserViewController(serviceType: "test", session: session)
browser.delegate = self
present(browser, animated: true)
}
@objc private func sendData() {
let stringToSend: String = "randomstring"
if let data = stringToSend.data(using: .utf8) {
try? session.send(data, toPeers: session.connectedPeers, with: .reliable)
} else {
print("error converting data from string")
}
}
}
// MARK: - MCSessionDelegate
extension ViewController: MCSessionDelegate {
func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
switch state {
case .notConnected:
print("Not connected: \(peerID.displayName).")
case .connecting:
print("Connecting: \(peerID.displayName).")
case .connected:
print("Connected: \(peerID.displayName).")
@unknown default:
fatalError()
}
}
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
if let receivedString = String(data: data, encoding: .utf8) {
print(receivedString)
} else {
print("error converting string from data")
}
}
func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer
<details>
<summary>英文:</summary>
I'm learning the basics of `MultipeerConnectivity` framework. I was trying to just send some data between two simulators, so I set up a small project and I've implemented the `MCBrowserViewControllerDelegate`, `MCNearbyServiceAdvertiserDelegate` and `MCSessionDelegate` protocols. I added three buttons, with a corresponding action each - names are pretty self-explanatory I guess:
- `advertiseButton`, with a `advertise()` associated method;
- `joinButton`, with a `join()` associated method;
- `sendDataButton`, with a `sendData()` associated method.
With my code, I was actually able to connect two simulator devices, since I get a "Connected" output printed in the console, however after a few seconds from the connection I get a bunch of errors logged in the console:
[GCKSession] Not in connected state, so giving up for participant [1935E9EC] on channel [0].
[GCKSession] Not in connected state, so giving up for participant [1935E9EC] on channel [1].
[GCKSession] Not in connected state, so giving up for participant [1935E9EC] on channel [2].
[GCKSession] Not in connected state, so giving up for participant [1935E9EC] on channel [3].
I might be wrong, but I guess it means that the connection was lost.
This happens **every time**, so it might be some implementation in my code done wrong.
My first question is: why does it happen and how can I fix it?
Moreover, if I tap on the `sendData` button on one simulator no data is received on the other simulator device, the `didReceive data` callback of `MCSessionDelegate` is never called. I'm almost sure it is due to the above log, however this occurs also if I press the button after the connection and slightly prior of the warning.
So, basically, what am I doing wrong in my code?
I'm using XCode 14.3, testing on simulators iPhone 14 and iPhone 14 Pro.
**EDIT:** just tested on real devices, same issue.
Here is the full sample project, just copy-paste it, add privacy settings in `Info.plist` if needed, then build&run:
import UIKit
import MultipeerConnectivity
final class ViewController: UIViewController {
// MARK: - Connectivity
private var peerID: MCPeerID!
private var session: MCSession!
private var nearbyServiceAdvertiser: MCNearbyServiceAdvertiser!
// MARK: - Buttons
private lazy var advertiseButton: UIButton = {
let button = UIButton()
button.addTarget(self, action: #selector(advertise), for: .touchUpInside)
button.setTitle("Advertise", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.titleLabel?.font = UIFont(name: "Arial", size: 24)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private lazy var joinButton: UIButton = {
let button = UIButton()
button.addTarget(self, action: #selector(join), for: .touchUpInside)
button.setTitle("Join", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.titleLabel?.font = UIFont(name: "Arial", size: 24)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private lazy var sendDataButton: UIButton = {
let button = UIButton()
button.addTarget(self, action: #selector(sendData), for: .touchUpInside)
button.setTitle("Send Data", for: .normal)
button.setTitleColor(.blue, for: .normal)
button.titleLabel?.font = UIFont(name: "Arial", size: 24)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(advertiseButton)
view.addSubview(joinButton)
view.addSubview(sendDataButton)
peerID = MCPeerID(displayName: UIDevice.current.name)
session = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .required)
session.delegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
advertiseButton.frame = CGRect(x: 0, y: 500, width: view.frame.size.width, height: 50)
joinButton.frame = CGRect(x: 0, y: 600, width: view.frame.size.width, height: 50)
sendDataButton.frame = CGRect(x: 0, y: 700, width: view.frame.size.width, height: 50)
}
// MARK: - Selectors
@objc private func advertise() {
nearbyServiceAdvertiser = MCNearbyServiceAdvertiser(peer: peerID, discoveryInfo: nil, serviceType: "test")
nearbyServiceAdvertiser.delegate = self
nearbyServiceAdvertiser.startAdvertisingPeer()
}
@objc private func join() {
let browser = MCBrowserViewController(serviceType: "test", session: session)
browser.delegate = self
present(browser, animated: true)
}
@objc private func sendData() {
let stringToSend: String = "randomstring"
if let data = stringToSend.data(using: .utf8) {
try? session.send(data, toPeers: session.connectedPeers, with: .reliable)
} else {
print("error converting data from string")
}
}
}
// MARK: - MCSessionDelegate
extension ViewController: MCSessionDelegate {
func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
switch state {
case .notConnected:
print("Not connected: \(peerID.displayName).")
case .connecting:
print("Connecting: \(peerID.displayName).")
case .connected:
print("Connected: \(peerID.displayName).")
@unknown default:
fatalError()
}
}
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
if let receivedString = String(data: data, encoding: .utf8) {
print(receivedString)
} else {
print("error converting string from data")
}
}
func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {
// no operations
}
func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) {
// no operations
}
func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) {
// no operations
}
}
// MARK: - MCBrowserViewControllerDelegate
extension ViewController: MCBrowserViewControllerDelegate {
func browserViewControllerDidFinish(_ browserViewController: MCBrowserViewController) {
browserViewController.dismiss(animated: true)
}
func browserViewControllerWasCancelled(_ browserViewController: MCBrowserViewController) {
browserViewController.dismiss(animated: true)
}
}
// MARK: - MCNearbyServiceAdvertiserDelegate
extension ViewController: MCNearbyServiceAdvertiserDelegate {
func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) -> Void) {
invitationHandler(true, session)
}
}
</details>
# 答案1
**得分**: 0
正如怀疑的那样,这确实是与我的代码相关的问题。显然,一旦建立连接,我必须停止搜索对等设备,否则设备可能会因继续搜索而断开连接。
所以,首先我需要声明一个新的变量,并将 `nearbyServiceAdvertiser` 也更改为可选的:
```swift
private var browserViewController: MCBrowserViewController?
private var nearbyServiceAdvertiser: MCNearbyServiceAdvertiser?
当然 join()
方法变成了:
@objc private func join() {
browserViewController = MCBrowserViewController(serviceType: "test", session: session)
browserViewController?.delegate = self
present(browserViewController!, animated: true)
}
然后在 MCSessionDelegate
的 didChange state
中,在 .connected
情况下:
nearbyServiceAdvertiser?.stopAdvertisingPeer()
browserViewController?.browser?.stopBrowsingForPeers()
这些步骤修复了问题。
可选的额外步骤:为了更好地理解可能的错误,让我们进一步重构代码:
do {
try session.send(data, toPeers: session.connectedPeers, with: .reliable)
} catch let error {
print(error.localizedDescription)
}
然而,我收到的控制台日志与我无法发送数据无关,实际上它们仍然存在。为什么以及它们的含义对我来说仍然不清楚,因为设备已连接,即使在日志之后我仍然能够发送数据。如果有人能够解释这一点,我会感激不尽。
英文:
As suspected, it was indeed an issue related to my code. Apparently I have to stop browsing for peers once a connection is enstablished, otherwise the devices might disconnect if they keep doing so.
So, first of all I need to declare a new variable, and change nearbyServiceAdvertiser
to optional also:
private var browserViewController: MCBrowserViewController?
private var nearbyServiceAdvertiser: MCNearbyServiceAdvertiser?
And of course join()
becomes:
@objc private func join() {
browserViewController = MCBrowserViewController(serviceType: "test", session: session)
browserViewController?.delegate = self
present(browserViewController!, animated: true)
}
Then in didChange state
of MCSessionDelegate
, in the .connected
case:
nearbyServiceAdvertiser?.stopAdvertisingPeer()
browserViewController?.browser?.stopBrowsingForPeers()
These steps fix the issue.
Optional: just for the sake of it, in order to better understand possible errors, let's refactor furthermore the code:
do {
try session.send(data, toPeers: session.connectedPeers, with: .reliable)
} catch let error {
print(error.localizedDescription)
}
However, the console logs I got are not related to the fact that I could not send data, in fact they are still present. Why and what do they mean is still unclear to me, as the devices are connected and I'm able to send data even after the logs. If someone is able to shine a light on this I'm thankful.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论