MultipeerConnectivity框架详细解析(二) —— 一个简单示例(一)

版本记录

版本号 时间
V1.0 2020.12.01 星期二

前言

MultipeerConnectivity框架支持点对点连接和附近设备的发现。是iOS 7 推出的众多新框架的一种,它拓宽了操作系统中应用的范围。其目的是使开发者可以创建通过Wi-Fi或蓝牙在近距离建立连接的应用。是在近距离设备间建立互动,交换数据和其他资源的很好的简单工具。接下来几篇我们就一起看一下这个框架。感兴趣的可以看下面几篇文章。
1. MultipeerConnectivity框架详细解析(一) —— 基本概览(一)

开始

首先看下主要内容:

在本教程中,您将学习如何在没有外部网络的设备之间传输数据。 您还将尝试创建聊天功能。内容来自翻译

接着看下写作环境:

Swift 5, iOS 14, Xcode 12

下面就是正文啦。

当被问到如何在设备之间传输数据时,大多数人会想到互联网。随着当今现代设备的发展以及蜂窝和Wi-Fi覆盖范围的扩大,这似乎合乎逻辑。但是,在某些情况下,您的用户可能无法使用Internet,他们仍然需要传输数据。想想一下您可能需要将照片发送给与您在一起的人的场景,也许是在国外旅行时。或者,您可能需要在拥挤的机场将文件发送给同事。这些是您的应用程序用户在现实世界中可能遇到的一些连接示例。

AppleMultipeer Connectivity框架引入了一种在应用程序用户之间发送和接收数据的方法。 Multipeer Connectivity使用设备的网络硬件(例如蓝牙和Wi-Fi),而无需互联网连接。想想您上次使用AirDrop将照片或网页发送给某人的想法。两种设备可能不在同一网络上,但是它们仍然能够将信息从一个设备发送到另一设备。 Multipeer Connectivity功能与支持AirDrop的框架相同,可将其包含在您的应用中。

在本教程中,您将学习如何:

  • Advertise your device to other users of your app.
  • Browse for users that are advertising.
  • Send custom data between devices.
  • Host a session multiple users can join.
  • Send data to multiple users at the same time.
  • Send files stored on your device to another user.

注意:对于本教程,您需要在两个不同的设备上运行该项目。这可以是物理设备或模拟器的任意组合。

首先,在启动程序文件夹中打开启动程序项目。

您将使用的应用程序Job Manager为经理和员工提供了一种相互创建和分配工作的方式。 它还允许员工与其他附近的员工托管或加入聊天室。

构建并运行,您将看到以下内容:

现在,您将继续为设备做广告。


Advertising Your Device

首先,打开Info.plist并添加以下条目:

  • Key: Privacy — Local Network Usage Description
  • ValueJob Manager needs to use your phone’s data to discover devices nearby

Apple需要此key来告知用户为什么您的应用程序将使用本地网络。 在联网中,局域网是设备共享相同通信方式的局域网。 使用Multipeer Connectivity时,您正在创建一个新的本地网络以供设备连接。

接下来,打开JobConnectionManager.swift并添加以下内容:

import MultipeerConnectivity

这将确保您可以在此文件中使用Multipeer Connectivity框架。

接下来,将以下内容添加到类声明中的JobConnectionManager.swift中:

// 1
private let session: MCSession
// 2
private let myPeerId = MCPeerID(displayName: UIDevice.current.name)
private let jobReceivedHandler: JobReceivedHandler?

init(_ jobReceivedHandler: JobReceivedHandler? = nil) {
  // 3
  session = MCSession(
    peer: myPeerId,
    securityIdentity: nil,
    encryptionPreference: .none)
  // 4
  self.jobReceivedHandler = jobReceivedHandler
}

以下是上述代码的细分:

  • 1) MCSession是用于处理设备之间所有通信的类。
  • 2) MCPeerID在本地网络上标识您的设备。 在此示例中,您使用的是您为手机设置的名称。
  • 3) 使用您的peer ID初始化会话。 您可以选择是否要对消息使用加密。 这里没有使用。
  • 4) 稍后您会在最后一行看到更多信息。

该应用程序使员工可以确定是否希望通过Advertising将工作发送到手机。 要实施Advertising,请将以下属性添加到JobConnectionManager

private static let service = "jobmanager-jobs"
private var nearbyServiceAdvertiser: MCNearbyServiceAdvertiser

MCNearbyServiceAdvertiser是将处理使您的设备可通过MCSession发现的类。 广告advertise的要求之一是您必须提供服务(service)。 下一步将涵盖这一点。

现在,将以下内容添加到JobConnectionManager的初始化程序中:

nearbyServiceAdvertiser = MCNearbyServiceAdvertiser(
  peer: myPeerId,
  discoveryInfo: nil,
  serviceType: JobConnectionManager.service)

在这里,您使用Service Type初始化nearbyServiceAdvertiserMultipeer Connectivity使用service type来限制其处理发现广告设备的方式。 在此项目中,JobConnectionManager将仅能发现使用jobmanager-jobs服务名称进行广告的设备。

接下来,将以下内容添加到 JobConnectionManager.swift中,位于类声明下方文件的末尾:

extension JobConnectionManager: MCNearbyServiceAdvertiserDelegate {
  func advertiser(
    _ advertiser: MCNearbyServiceAdvertiser,
    didReceiveInvitationFromPeer peerID: MCPeerID,
    withContext context: Data?,
    invitationHandler: @escaping (Bool, MCSession?) -> Void
  ) {
  }
}

设备发布可用于工作的广告后,就需要一种方法来处理连接和接收工作的请求。 MCNearbyServiceAdvertiserDelegate需要处理此请求。 在这里,您可以决定是否要让您的应用自动接受邀请,或者询问用户是否要允许连接。

要处理邀请,请将以下内容添加到Advertiser(_:didReceiveInvitationFromPeer:withContext:invitationHandler :)

guard 
  let window = UIApplication.shared.windows.first,
  // 1
  let context = context,
  let jobName = String(data: context, encoding: .utf8) 
else {
  return
}
let title = "Accept \(peerID.displayName)'s Job"
let message = "Would you like to accept: \(jobName)"
let alertController = UIAlertController(
  title: title,
  message: message,
  preferredStyle: .alert)
alertController.addAction(UIAlertAction(
  title: "No",
  style: .cancel
) { _ in
  // 2
  invitationHandler(false, nil)
})
alertController.addAction(UIAlertAction(
  title: "Yes",
  style: .default
) { _ in
  // 3
  invitationHandler(true, self.session)
})
window.rootViewController?.present(alertController, animated: true)

在上面的代码中,当应用收到邀请时,它会显示一条alert,要求用户接受或拒绝该工作。 这里需要重点注意三件事:

  • 1) 传入的上下文context将转换为字符串。 当您转到有关发送数据的部分时,您会看到这种情况。 目前,您需要了解可以根据需要在此处传递和转换任何类型的Data
  • 2) InvitationHandler闭包表示广告设备是否要接受连接。 这是您取消邀请的方式。
  • 3) 如果用户选择接受连接,则将当前的MCSession传递给InvitationHandler

现在,将以下内容添加到JobConnectionManager的初始化程序的末尾:

super.init()
nearbyServiceAdvertiser.delegate = self

在这里,您为广告设置了delegate,发送邀请时,它将显示alert

您几乎可以完成设备宣传的工作! 仅需几个步骤。

将以下属性添加到JobConnectionManager

var isReceivingJobs: Bool = false {
  didSet {
    if isReceivingJobs {
      nearbyServiceAdvertiser.startAdvertisingPeer()
      print("Started advertising")
    } else {
      nearbyServiceAdvertiser.stopAdvertisingPeer()
      print("Stopped advertising")
    }
  }
}

您可能不希望自己的设备一直在宣传自己。 在这里,您可以根据isReceivingJobs的值启动或停止广告自己。

1. Binding the UI

接下来,在JobListView.swift中,添加以下属性:

@ObservedObject var jobConnectionManager: JobConnectionManager

然后用下面的初始化器替换下面的:

init(jobListStore: JobListStore = JobListStore()) {
  self.jobListStore = jobListStore
  jobConnectionManager = JobConnectionManager { job in
    jobListStore.jobs.append(job)
  }
}

在上面的代码中,您正在做两件事:

  • 1) 添加观察到的属性来管理你的广告状态。
  • 2) 初始化相同的属性。 JobConnectionManager在收到作业时将通过此关闭发送作业。 此代码会将收到的作业追加到jobListStore,稍后再使用。

最后,将JobListView.swift中的headerView替换为以下内容:

var headerView: some View {
  Toggle("Receive Jobs", isOn: $jobConnectionManager.isReceivingJobs)
}

通过将Toggle绑定到isReceivingJobs,您的应用现在将打开和关闭广告。

现在,从JobListView.swift中删除以下属性,因为该属性不再使用:

@State private var isReceivingJobs = false

构建并运行,然后打开和关闭RECEIVE JOBS开关。 您将在Xcode控制台中看到以下内容:


Discovering Devices

现在您可以播发设备,下一步就是发现设备。 首先向JobConnectionManager.swift添加以下属性:

@Published var employees: [MCPeerID] = []
private var nearbyServiceBrowser: MCNearbyServiceBrowser

第一个新属性,employees,存储通过上一节中设置的服务发现的设备。

下一个属性是MCNearbyServiceBrowser。 此类处理发现打开广告的设备的所有工作。

接下来,在JobConnectionManager的初始化程序中,在对super的调用之前初始化ServiceBrowser

nearbyServiceBrowser = MCNearbyServiceBrowser(
  peer: myPeerId, 
  serviceType: JobConnectionManager.service)

MCNearbyServiceBrowser看起来类似于MCNearbyServiceAdvertiser。 它需要一个peer ID和服务。 如果这两个属性都使用JobConnectionManager.service,它们将能够发现彼此并进行通信。

将以下内容添加到JobConnectionManager.swift的末尾:

extension JobConnectionManager: MCNearbyServiceBrowserDelegate {
  func browser(
    _ browser: MCNearbyServiceBrowser,
    foundPeer peerID: MCPeerID,
    withDiscoveryInfo info: [String: String]?
  ) {
    // 1
    if !employees.contains(peerID) {
      employees.append(peerID)
    }
  }

  func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {
    guard let index = employees.firstIndex(of: peerID) else { return }
    // 2
    employees.remove(at: index)
  }
}

实施MCNearbyServiceBrowserDelegate允许您在发现对等方或设备时响应事件。

这里发生两件事:

  • 1) 当浏览发现对等方时,会将其添加到employees中。
  • 2) 相反,当浏览放开peer时,会将其从列表中删除。

现在,将以下内容添加到JobConnectionManager的初始化程序的末尾:

nearbyServiceBrowser.delegate = self

这样可以确保在发现或丢失设备时调用上一步中的代码。

在设置UI以显示设备之前,请添加以下方法以在JobConnectionManager中启用和禁用浏览:

func startBrowsing() {
  nearbyServiceBrowser.startBrowsingForPeers()
}

func stopBrowsing() {
  nearbyServiceBrowser.stopBrowsingForPeers()
}

默认情况下,您的服务浏览器不会浏览同伴。 您可以通过启动和停止此服务来控制状态。

1. Rendering the List

要渲染该列表,请首先在JobView.swiftJobListRowView.swift中添加以下属性:

@EnvironmentObject var jobConnectionManager: JobConnectionManager

由于要在工作视图之间共享模型数据,因此请将属性设置为EnvironmentObject

接下来,在JobListView.swiftbody中,将节的正文Section替换为以下内容:

ForEach(jobListStore.jobs) { job in
  JobListRowView(job: job)
    .environmentObject(jobConnectionManager)
}

现在,这将与JobListRowView共享您的jobConnectionManager

您需要在另一个视图上设置jobConnectionManager。 在JobListRowView.swift中,将NavigationLinkdestination替换为以下内容:

JobView(job: job).environmentObject(jobConnectionManager)

与上一步一样,它与JobView共享jobConnectionManager

同样,您快到了!

JobView.swift中,将在最后一节Section中找到的EmptyView()替换为以下内容:

ForEach(jobConnectionManager.employees, id: \.self) { employee in
  HStack {
    Text(employee.displayName)
      .font(.headline)
    Spacer()
    Image(systemName: "arrowshape.turn.up.right.fill")
  }
}

这将在浏览时找到的每个员工中填充该部分。 然后,当发现或丢失它们时,会将它们添加到此列表中或从中删除。

最后,在JobView.swift中,在设置导航标题后,将以下内容添加到列表List的末尾:

.onAppear {
  jobConnectionManager.startBrowsing()
}
.onDisappear {
  jobConnectionManager.stopBrowsing()
}

作业视图出现时,它将开始浏览同伴,而该视图消失时将停止。 你们都准备好了!

在两个不同的设备上构建并运行。 您会看到以下内容:

在一个设备上,添加一个作业,然后从主列表中选择该作业。

在另一台设备上,打开RECEIVE JOBS。 现在,您将看到以下内容:

最后,关闭第二台设备的RECEIVE JOBS,这会将对等方从第一台设备的列表中删除:


Inviting Peers

现在,大部分工作已经完成,您可以开始在设备之间发送作业了。 第一步是将邀请设备连接在一起。

打开JobConnectionManager.swift并添加一个新属性:

private var jobToSend: JobModel?

当两个设备连接时,它异步发生并通过委托方法发生。 此属性将临时保存您要发送的作业。

接下来,将以下方法添加到类中:

func invitePeer(_ peerID: MCPeerID, to job: JobModel) {
  // 1
  jobToSend = job
  // 2
  let context = job.name.data(using: .utf8)
  // 3
  nearbyServiceBrowser.invitePeer(
    peerID, 
    to: session,
    withContext: context,
    timeout: TimeInterval(120))
}

以下是逐步添加的内容的细分:

  • 1) 保存工作,直到需要为止
  • 2) 创建一个上下文context。 这会将工作名称序列化为Data
  • 3) 要求您的服务浏览器使用其他设备的peer ID邀请对方,并传递序列化的作业名称作为context

从一台设备邀请对等方将提示另一台设备使用您之前添加的advertiser(_:didReceiveInvitationFromPeer:withContext:invitationHandler:)进行连接。 浏览设备创建上下文,广告对等方接收该上下文以帮助其确定是否要接受作业。

JobView.swift中,通过将以下内容添加到组成该行的HStack的末尾,向显示附近设备的行添加轻击手势:

.onTapGesture {
  jobConnectionManager.invitePeer(employee, to: job)
}

从列表中点击附近的设备或对等方将触发邀请过程。

构建并运行。 同样,在一台设备上创建工作,然后在另一台设备上打开RECEIVE JOBS。 从主列表中选择工作,然后选择附近的设备。 您会在第二台设备上看到邀请,如下所示:


Sending Data Between Devices

要开始在设备之间发送工作,请首先将以下方法添加到JobConnectionManager.swift中:

private func send(_ job: JobModel, to peer: MCPeerID) {
  do {
    let data = try JSONEncoder().encode(job)
    try session.send(data, toPeers: [peer], with: .reliable)
  } catch {
    print(error.localizedDescription)
  }
}

在这里,您正在编码要发送的工作。 然后,您将使用MCSessionsend(_:toPeers:with :)。 此方法需要三项:

  • 1) 您要发送的Data对象。 在这种情况下,这是序列化的工作。
  • 2) 您要将数据发送到的一组对等体。 由于您仅将工作发送到选定的设备,因此数组中只有一个对等体。
  • 3) 发送数据的mode。 您不必在这里使用reliable,因为您一次只发送一份工作。 如果您要发送多个工作并想保证顺序,这将变得尤为重要。

接下来,将以下代码块添加到JobConnectionManager.swift的末尾:

extension JobConnectionManager: MCSessionDelegate {
  func session(
    _ session: MCSession,
    peer peerID: MCPeerID,
    didChange state: MCSessionState
  ) {
    switch state {
    case .connected:
      print("Connected")
    case .notConnected:
      print("Not connected: \(peerID.displayName)")
    case .connecting:
      print("Connecting to: \(peerID.displayName)")
    @unknown default:
      print("Unknown state: \(state)")
    }
  }

  func session(
    _ session: MCSession,
    didReceive data: Data,
    fromPeer peerID: MCPeerID
  ) {
  }

  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?
  ) {}
}

这里似乎发生了很多事情,但是实际上很简单。 要发送和接收数据,您需要遵循MCSessionDelegate。 这些方法都是required的,但是本教程的这一部分将仅重点介绍其中的两种方法。

查看session(_:peer:didChange:)。 每当与另一个对等方的连接状态更改时,都会调用此方法。 发送工作时,一台设备要求连接到另一台设备。 如果它授予许可并建立连接,则将在连接状态下调用此方法。

session(_:peer:didChange :)中,替换以下打印语句:

case .connected:
  print("Connected")

替换为:

case .connected:
  guard let jobToSend = jobToSend else { return }
  send(jobToSend, to: peerID)

现在,一旦设备建立连接,尝试发送工作的设备将调用您上面添加的send(_:to :)

要处理接收工作,请在JobConnectionManager.swift中将session(_:didReceive:fromPeer :)替换为以下内容:

func session(
  _ session: MCSession,
  didReceive data: Data,
  fromPeer peerID: MCPeerID
) {
  guard let job = try? JSONDecoder()
    .decode(JobModel.self, from: data) else { return }
  DispatchQueue.main.async {
    self.jobReceivedHandler?(job)
  }
}

当工作发送到广告设备时,它将数据传递给此方法。 在这里,将数据解码为JobModel。 在主线程上调用jobReceivedHandler,因为它需要更新UI。

现在,将以下内容添加到JobConnectionManager的初始化程序的末尾:

session.delegate = self

最后一步确保连接到设备将调用您的代码以发送工作。 它还处理解码并将工作添加到广告设备。

构建并运行。 遵循相同的步骤,在一个设备上添加工作,然后在另一设备上打开RECEIVE JOBS。 在第一台设备上,选择在工作视图中找到的对等方。 在第二台设备上,接受邀请以接收工作。 现在,您将在两种设备上看到相同的工作:


Browsing for Sessions

在构建应用程序的聊天功能时,本教程的最后一部分将重点介绍几个不同的项目。 如果打开ChatConnectionManger.swift,您会看到很多代码与JobConnectionManager.swift中的代码相似。

构建并运行,然后选择Messages选项卡。 您会看到以下内容:

Messages的状态从您从本教程的第一部分开始的地方开始。 如果您选择Host a Chat Session,它将开始使用MCNearbyServiceAdvertiser广告您的设备。 但是,Messages将使用不同的服务类型:jobmanager-chat

由于hosting a session已经完成,因此您需要一种加入session的方法。 处理工作时,您编写了自定义代码以使用MCNearbyServiceBrowser查找会话。 对于这一部分,您将使用Apple的默认UI浏览会话。

首先,在ChatConnectionManager.swift中,将join()替换为以下内容:

func join() {
  // 1
  peers.removeAll()
  messages.removeAll()
  session = MCSession(
    peer: myPeerId,
    securityIdentity: nil,
    encryptionPreference: .required)
  session?.delegate = self
  guard 
    let window = UIApplication.shared.windows.first,
    let session = session 
  else { return }
  // 2
  let mcBrowserViewController = MCBrowserViewController(
    serviceType: ChatConnectionManager.service,
    session: session)
  window.rootViewController?.present(mcBrowserViewController, animated: true)
}

上面的代码是这样的:

  • 1) 第一部分清理所有消息或peers并为您创建一个新会话。
  • 2) 通过使用MCBrowserViewController,您可以访问内置UI,以显示正在发布服务的peers

接下来,将以下内容添加到ChatConnectionManager.swift的末尾:

extension ChatConnectionManager: MCBrowserViewControllerDelegate {
  func browserViewControllerDidFinish(
    _ browserViewController: MCBrowserViewController
  ) {
    browserViewController.dismiss(animated: true) {
      self.connectedToChat = true
    }
  }

  func browserViewControllerWasCancelled(
    _ browserViewController: MCBrowserViewController
  ) {
    session?.disconnect()
    browserViewController.dismiss(animated: true)
  }
}

此代码将处理出现MCBrowserViewController时可能发生的两种情况:

  • 1) 用户已选择要加入的会话。
  • 2) 用户已取消浏览。

接下来,将以下代码行添加到join()的末尾:

mcBrowserViewController.delegate = self

这将确保您上一步中的代码得以执行。

最后,在ChatConnectionManager.swift中,将advertiser(_:didReceiveInvitationFromPeer:withContext:invitationHandler :)替换为以下代码:

func advertiser(
  _ advertiser: MCNearbyServiceAdvertiser,
  didReceiveInvitationFromPeer peerID: MCPeerID,
  withContext context: Data?,
  invitationHandler: @escaping (Bool, MCSession?) -> Void
) {
  invitationHandler(true, session)
}

在处理发送工作时,您使用此方法来显示alert,以询问用户是否接受工作。 在这里,默认情况下,您允许连接到hosting session

在这两种设备上构建并运行,然后转到Messages。 从一台设备托管一个会话。 在第二台设备上,选择Join a Chat Session。 您会看到以下内容:

您可以在浏览器视图控制器中选择一个对等方。 看到Connected后,选择Done。 然后,您将看到以下内容:

1. Seeing Connected Peers

如果您看不到谁加入了,则聊天室没有帮助。 因此,在session(_:peer:didChange :)中,发现以下cases

case .connected:
  print("Connected")
case .notConnected:
  print("Not Connected")

然后将它们替换为下面的代码:

case .connected:
  if !peers.contains(peerID) {
    // 1
    DispatchQueue.main.async {
      self.peers.insert(peerID, at: 0)
    }
    // 2
    if isHosting {
      sendHistory(to: peerID)
    }
  }
case .notConnected:
  DispatchQueue.main.async {
    // 3
    if let index = self.peers.firstIndex(of: peerID) {
      self.peers.remove(at: index)
    }
    // 4
    if self.peers.isEmpty && !self.isHosting {
      self.connectedToChat = false
    }
  }

在上一节中,您使用此方法来知道何时发送工作。 在这里,您正在使用它执行以下操作:

  • 1) 如果用户连接到聊天,请将其添加到对等方列表中。
  • 2) 如果您是host,请将整个聊天记录发送给新连接的用户。 稍后将实现。
  • 3) 当某人断开连接时,请将其从同伴列表中删除。
  • 4) 如果主机host和所有其他对等方离开,则自动结束会话。

在这两种设备上构建和运行,在一个设备上托管hosting,在另一个设备上加入。 现在,您会在聊天顶部看到两个用户,如下所示:


Sending Data to Multiple Devices

现在您就可以开始在用户之间发送消息了! 在ChatConnectionManager.swift中,将send(_ :)替换为以下代码:

func send(_ message: String) {
  // 1
  let chatMessage = ChatMessage(
    displayName: myPeerId.displayName, 
    body: message)
  messages.append(chatMessage)
  // 2
  guard 
    let session = session,
    let data = message.data(using: .utf8),
    !session.connectedPeers.isEmpty 
  else { return }

  do {
    // 3
    try session.send(data, toPeers: session.connectedPeers, with: .reliable)
  } catch {
    print(error.localizedDescription)
  }
}

以下是上述代码的细分:

  • 1) 创建带有本地使用所需相关数据的ChatMessage
  • 2) 编码消息字符串。
  • 3) 使用MCSession发送到所有连接的peers

接下来,将session(_:didReceive:fromPeer :)替换为以下内容:

func session(
  _ session: MCSession,
  didReceive data: Data,
  fromPeer peerID: MCPeerID
) {
  guard let message = String(data: data, encoding: .utf8) else { return }
  let chatMessage = ChatMessage(displayName: peerID.displayName, body: message)
  DispatchQueue.main.async {
    self.messages.append(chatMessage)
  }
}

在这两种设备上构建并运行,并在两者之间创建聊天会话。 您会看到以下内容:

如果您退出聊天并返回,您将看到聊天记录已消失。 您可以通过在用户加入时将整个聊天记录发送给用户来解决此问题。

sendHistory(to :)替换为以下内容:

func sendHistory(to peer: MCPeerID) {
  // 1
  let tempFile = URL(fileURLWithPath: NSTemporaryDirectory())
    .appendingPathComponent("messages.data")
  guard let historyData = try? JSONEncoder().encode(messages) else { return }
  // 2
  try? historyData.write(to: tempFile)
  // 3
  session?.sendResource(
    at: tempFile,
    withName: "Chat_History",
    toPeer: peer
  ) { error in
    if let error = error {
      print(error.localizedDescription)
    }
  }
}

此代码将在hosting设备上执行。 这是发生了什么:

  • 1) 在您的临时目录中创建URL
  • 2) 将聊天记录写入此URL上的文件。
  • 3) 使用MCSession发送资源而不是发送Data

由于您不是直接发送数据,因此需要使用另一种MCSessionDelegate委托方法。 将session(_:didFinishReceivingResourceWithName:fromPeer:at:withError:)替换为以下内容:

func session(
  _ session: MCSession,
  didFinishReceivingResourceWithName resourceName: String,
  fromPeer peerID: MCPeerID,
  at localURL: URL?,
  withError error: Error?
) {
  // 1
  guard 
    let localURL = localURL,
    let data = try? Data(contentsOf: localURL),
    // 2 
    let messages = try? JSONDecoder().decode([ChatMessage].self, from: data) 
  else { return }

  DispatchQueue.main.async {
    // 3
    self.messages.insert(contentsOf: messages, at: 0)
  }
}

此代码在连接到会话的对等方peer上执行。 这是正在发生的事情:

  • 1) 如果资源已成功本地保存在设备上,您将在此处收到URL
  • 2) 该文件的内容被解码为ChatMessage
  • 3) 这些消息会插入您的本地消息中,当您加入聊天时会显示这些消息。

在这两种设备上构建并运行。 创建聊天会话并在两个设备之间发送消息,如下所示:

在用于加入会话的设备上,选择Leave

重新加入同一会话,您将看到完整的聊天记录,如下所示:

现在,您应该可以使用Multipeer Connectivity并在没有Internet的情况下在设备之间发送通信了。 还有更多的东西要学习,例如在设备之间发送和接收流。

您可以通过查看更多的iOS & Swift Tutorials或参考Apple’s documentation来了解更多信息。

后记

本篇主要讲述了MultipeerConnectivity的一个简单示例,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容