本文概述
- 概念与类
- 基本规则
- MultipeerConnectivity入门
- 连接设备
- 传送讯息
- 结论
MPC在这里处理了许多基础需求基础架构:
- 多种网络接口支持(蓝牙, WiFi和以太网)
- 设备检测
- 加密安全
- 小消息传递
- 文件传输
文章图片
多对等会话生命周期:
- MCNearbyServiceAdvertiser.startAdvertisingForPeers()
- MCNearbyServiceBrowser.startBrowsingForPeers()
- MCNearbyServiceBrowserDelegate.browser(:foundPeer:withDiscoveryInfo ??
- MCNearbyServiceBrowser.invitePeer(… )
- MCNearbyServiceAdvertiserDelegate.didReceiveInvitationFromPeer(… )
- 在didReceiveInvitation中调用InvitationHandler
- 创建MCSession
- MCSession.send(… )
- MCSessionDelegate.session(_:didReceive:data:peerID)
- MCSession.disconnect()
那里有许多MultipeerConnectivity教程和示例, 旨在引导iOS开发人员完成基于MPC的应用程序的实现。但是, 以我的经验来看, 它们通常是不完整的, 往往会掩盖MPC的一些重要潜在绊脚石。在本文中, 我希望引导读者逐步了解该应用程序的基本实现, 并指出我发现容易卡住的地方。
概念与类 MPC基于少数几个类。让我们浏览一下常见的列表, 并加深对框架的理解。
- MCSession –会话管理其关联对等方之间的所有通信。你可以通过会话发送消息, 文件和流, 当从连接的对等方收到消息, 文件和流之一时, 将通知其委托。
- MCPeerID –对等ID可让你识别会话中的各个对等设备。它有一个相关的名称, 但要小心:具有相同名称的对等ID不会被认为是相同的(请参见下面的基本规则)。
- MCNearbyServiceAdvertiser –广告商允许你将服务名称广播到附近的设备。这使他们可以连接到你。
- MCNearbyServiceBrowser –使用浏览器, 你可以使用MCNearbyServiceAdvertiser搜索设备。结合使用这两个类, 可以发现附近的设备并创建对等连接。
- MCBrowserViewController –这提供了一个非常基本的UI, 用于浏览附近的设备服务(通过MCNearbyServiceAdvertiser出售)。虽然适用于某些用例, 但我们不会使用它, 因为以我的经验, MCP的最佳方面之一就是它的无缝性。
- 设备由MCPeerID对象标识。这些是表面上包裹的字符串, 实际上可以用简单的名称初始化。尽管可以使用相同的字符串创建两个MCPeerID, 但是它们并不相同。因此, 绝不应复制或重新创建MCPeerID。它们应该在应用程序中传递。如有必要, 可以使用NSArchiver存储它们。
- 尽管缺少文档, 但MCSession可用于在两个以上的设备之间进行通信。但是, 以我的经验, 利用这些对象的最稳定方法是为设备与之交互的每个对等对象创建一个。
- 当你的应用程序在后台运行时, MPC将无法运行。当应用程序进入后台时, 你应该断开连接并拆除所有MCSession。在任何后台任务中, 请勿尝试做最少的操作。
由于MPC依赖于系统提供的API, 并且与实际对象(其他设备以及它们之间的共享” 网络” )相关联, 因此非常适合单例模式。虽然经常使用过度, 但单例非常适合此类共享资源。
这是我们单身人士的定义:
class MPCManager: NSObject {
var advertiser: MCNearbyServiceAdvertiser!
var browser: MCNearbyServiceBrowser!static let instance = MPCManager()let localPeerID: MCPeerID
let serviceType = "MPC-Testing"var devices: [Device] = []override init() {
if let data = http://www.srcmini.com/UserDefaults.standard.data(forKey:"peerID"), let id = NSKeyedUnarchiver.unarchiveObject(with: data) as? MCPeerID {
self.localPeerID = id
} else {
let peerID = MCPeerID(displayName: UIDevice.current.name)
let data = http://www.srcmini.com/try? NSKeyedArchiver.archivedData(withRootObject: peerID)
UserDefaults.standard.set(data, forKey:"peerID")
self.localPeerID = peerID
}super.init()self.advertiser = MCNearbyServiceAdvertiser(peer: localPeerID, discoveryInfo: nil, serviceType: self.serviceType)
self.advertiser.delegate = selfself.browser = MCNearbyServiceBrowser(peer: localPeerID, serviceType: self.serviceType)
self.browser.delegate = self
}
}
请注意, 我们将MCPeerID存储在用户默认设置中(通过NSKeyedArchiver), 然后重新使用它。如上所述, 这很重要, 如果无法以某种方式对其进行缓存, 则可能会导致更深层次的错误。
这是我们的设备类, 我们将使用它来跟踪已发现的设备及其状态:
class Device: NSObject {
let peerID: MCPeerID
var session: MCSession?
var name: String
var state = MCSessionState.notConnectedinit(peerID: MCPeerID) {
self.name = peerID.displayName
self.peerID = peerID
super.init()
}func invite() {
browser.invitePeer(self.peerID, to: self.session!, withContext: nil, timeout: 10)
}}
现在我们已经建立了最初的课程, 现在该退后一步, 考虑一下浏览器和广告商之间的相互作用。在MPC中, 设备可以宣传其提供的服务, 并且可以浏览其他设备上感兴趣的服务。由于我们仅专注于使用应用程序进行设备到设备的通信, 因此我们将宣传和浏览相同的服务。
在传统的客户端/服务器配置中, 一个设备(服务器)将播发其服务, 客户端将浏览它们。由于我们是平等主义者, 因此我们不需要为设备指定角色;我们将为每台设备做广告和浏览。
我们需要在MPCManager中添加一种方法, 以在发现设备时创建它们并在设备数组中对其进行跟踪。我们的方法将使用MCPeerID, 查找具有该ID的现有设备, 如果找到则将其返回。如果我们还没有现有设备, 则创建一个新设备并将其添加到设备阵列中。
func device(for id: MCPeerID) ->
Device {
for device in self.devices {
if device.peerID == id { return device }
}let device = Device(peerID: id)self.devices.append(device)
return device
}
设备开始播发广告后, 另一个浏览设备可以尝试将其连接。在这种情况下, 我们需要将委托方法添加到MPCSession类中, 以处理来自广告客户的传入委托调用:
extension MPCManager: MCNearbyServiceAdvertiserDelegate {
func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: @escaping (Bool, MCSession?) ->
Void) {
let device = MPCManager.instance.device(for: peerID)
device.connect()
invitationHandler(true, device.session)
}
}
…我们的设备上用于创建MCSession的方法:
func connect() {
if self.session != nil { return }self.session = MCSession(peer: MPCManager.instance.localPeerID, securityIdentity: nil, encryptionPreference: .required)
self.session?.delegate = self
}
…最后是当我们的浏览器发现广告商时触发邀请的方法:
extension MPCManager: MCNearbyServiceBrowserDelegate {
func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) {
let device = MPCManager.instance.device(for: peerID)
device.invite(with: self.browser)
}
现在, 我们忽略了withDiscoveryInfo参数;我们可以使用它根据可用的设备过滤掉特定设备(这与我们在上面的MCNearbyServiceAdvertiser的DiscoveryInfo参数中提供的词典相同)。
连接设备 现在, 我们已经完成了所有客房整理工作, 接下来就可以开始连接设备的实际业务了。
在MPCSession的初始化方法中, 我们设置了广告客户和代表。当我们准备开始连接时, 我们需要同时启动它们。这可以通过应用程序委托的didFinishLaunching方法完成, 也可以在适当的时候完成。这是我们将添加到类中的start()方法:
func start() {
self.advertiser.startAdvertisingPeer()
self.browser.startBrowsingForPeers()
}
这些通话将意味着你的应用将开始通过WiFi广播其状态。请注意, 你无需连接到WiFi网络即可工作(但必须将其打开)。
当设备响应邀请并启动其MCSession时, 它将开始从会话中接收委托回调。我们会将这些处理程序添加到我们的设备对象中;我们暂时将忽略其中的大多数:
extension Device: MCSessionDelegate {
public func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
self.state = state
NotificationCenter.default.post(name: Multipeer.Notifications.deviceDidChangeState, object: self)
}public func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { }public func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) { }public func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) { }public func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) { }}
目前, 我们主要关注的是session(_:peer:didChangeState :)回调。每当设备转换到新状态(未连接, 正在连接和已连接)时, 都会调用此方法。我们将对此进行跟踪, 以便我们建立所有已连接设备的列表:
extension MPCManager {
var connectedDevices: [Device] {
return self.devices.filter { $0.state == .connected }
}
}
传送讯息 现在我们已经连接了所有设备, 是时候开始真正地来回发送消息了。 MPC在这方面提供了三种选择:
- 我们可以发送一个字节块(一个数据对象)
- 我们可以发送文件
- 我们可以打开到其他设备的流
struct Message: Codable {
let body: String
}
我们还将向设备添加扩展程序, 以发送以下其中一项:
extension Device {
func send(text: String) throws {
let message = Message(body: text)
let payload = try JSONEncoder().encode(message)
try self.session?.send(payload, toPeers: [self.peerID], with: .reliable)
}
}~~~swiftFinally, we'll need to modify our `Device.session(_:didReceive:fromPeer)` code to receive the message, parse it, and notify any interested objects about it:
【串通(iOS中具有MultipeerConnectivity的附近设备联网)】静态let messageReceivedNotification = Notification.Name(” DeviceDidReceiveMessage” )公共功能会话(_ session:MCSession, didReceive数据:Data, 来自对等方peerID:MCPeerID){如果let message =试试? JSONDecoder()。decode(Message.self, 来自:数据){NotificationCenter.default.post(名称:Device.messageReceivedNotification, 对象:message, userInfo:[” from” :self])}}
## DisconnectionsNow that we've got a connection created between multiple devices, we have to be able to both disconnect on demand and also handle system interruptions. One of the undocumented weaknesses of MPC is that it doesn't function in the background. We need to observe the `UIApplication.didEnterBackgroundNotification` notification, and make sure that we shut down all our sessions. Failure to do this will lead to undefined states in the sessions and devices and can cause lots of confusing, hard-to-track-down errors. There is a temptation to use a background task to keep your sessions around, in case the user jumps back into your app. However, this is a bad idea, as MPC will usually fail within the first second of being backgrounded.When your app returns to the foreground, you can rely on MPC's delegate methods to rebuild your connections.In our MPCSession's `start()` method, we'll want to observe this notification and add code to handle it and shut down all our sessions.~~~swiftfunc start() {
self.advertiser.startAdvertisingPeer()
self.browser.startBrowsingForPeers()NotificationCenter.default.addObserver(self, selector: #selector(enteredBackground), name: Notification.Name.UIApplicationDidEnterBackground, object: nil)
}@objc func enteredBackground() {
for device in self.devices {
device.disconnect()
}
}func disconnect() {
self.session?.disconnect()
self.session = nil
}
结论 本文介绍了构建基于MultipeerConnectivity的应用程序的网络组件所需的体系结构。完整的源代码(在Github上可用)提供了最小的用户界面包装器, 使你可以查看连接的设备并在它们之间发送消息。
MPC可以在附近的设备之间提供近乎无缝的连接, 而无需担心WiFi网络, 蓝牙或复杂的客户端/服务器体操。能够以短暂的游戏时间快速配对几部手机, 或连接两台设备进行共享, 这是典型的Apple方式。
Github上的https://github.com/bengottlieb/MultipeerExample提供了该项目的源代码。
设计使用AFNetworking的iOS? Model-View-Controller(MVC)设计模式非常适合维护代码库, 但有时由于DRY代码, 集中式网络日志记录(尤其是速率限制)等问题, 你需要一个类来处理网络。阅读有关在iOS集中式和解耦网络中使用Singleton类进行处理的所有信息:具有Singleton类的AFNetworking教程
推荐阅读
- ARKit演示(增强现实电影制作)
- 使用静态模式(Swift MVVM教程)
- iOS中的RxSwift和动画
- Android更改活动名称
- 使用AppleScript单击菜单项
- Apple Mail全功能HTML签名
- 用LoadRunner录制手机APP脚本
- wpf开源控件MahApps.Metro
- Applying and Inverting Transformations [on mask]