前面已经说了GameKit相关的蓝牙操作类从iOS7已经全部过期,苹果官方推荐使用MultipeerConnectivity代替。但是应该了解,MultipeerConnectivity.framework并不仅仅支持蓝牙连接,准确的说它是一种支持Wi-Fi网络、P2P Wi-Fi已经蓝牙个人局域网的通信框架,它屏蔽了具体的连接技术,让开发人员有统一的接口编程方法。通过MultipeerConnectivity连接的节点之间可以安全的传递信息、流或者其他文件资源而不必通过网络服务。此外使用MultipeerConnectivity进行近场通信也不再局限于同一个应用之间传输,而是可以在不同的应用之间进行数据传输(当然如果有必要的话你仍然可以选择在一个应用程序之间传输)。
要了解MultipeerConnectivity的使用必须要清楚一个概念:广播(Advertisting)和发现(Disconvering),这很类似于一种Client-Server模式。假设有两台设备A、B,B作为广播去发送自身服务,A作为发现的客户端。一旦A发现了B就试图建立连接,经过B同意二者建立连接就可以相互发送数据。在使用GameKit框架时,A和B既作为广播又作为发现,当然这种情况在MultipeerConnectivity中也很常见。
A.广播
无论是作为服务器端去广播还是作为客户端去发现广播服务,那么两个(或更多)不同的设备之间必须要有区分,通常情况下使用MCPeerID对象来区分一台设备,在这个设备中可以指定显示给对方查看的名称(display name)。另外不管是哪一方,还必须建立一个会话MCSession用于发送和接受数据。通常情况下会在会话的-(void)session:(MCSession )session peer:(MCPeerID )peerID didChangeState:(MCSessionState)state代理方法中跟踪会话状态(已连接、正在连接、未连接);在会话的-(void)session:(MCSession )session didReceiveData:(NSData )data fromPeer:(MCPeerID *)peerID代理方法中接收数据;同时还会调用会话的-(void)sendData: toPeers:withMode: error:方法去发送数据。
广播作为一个服务器去发布自身服务,供周边设备发现连接。在MultipeerConnectivity中使用MCAdvertiserAssistant来表示一个广播,通常创建广播时指定一个会话MCSession对象将广播服务和会话关联起来。一旦调用广播的start方法周边的设备就可以发现该广播并可以连接到此服务。在MCSession的代理方法中可以随时更新连接状态,一旦建立了连接之后就可以通过MCSession的connectedPeers获得已经连接的设备。
B.发现
前面已经说过作为发现的客户端同样需要一个MCPeerID来标志一个客户端,同时会拥有一个MCSession来监听连接状态并发送、接受数据。除此之外,要发现广播服务,客户端就必须要随时查找服务来连接,在MultipeerConnectivity中提供了一个控制器MCBrowserViewController来展示可连接和已连接的设备(这类似于GameKit中的GKPeerPickerController),当然如果想要自己定制一个界面来展示设备连接的情况你可以选择自己开发一套UI界面。一旦通过MCBroserViewController选择一个节点去连接,那么作为广播的节点就会收到通知,询问用户是否允许连接。由于初始化MCBrowserViewController的过程已经指定了会话MCSession,所以连接过程中会随时更新会话状态,一旦建立了连接,就可以通过会话的connected属性获得已连接设备并且可以使用会话发送、接受数据。
import UIKit
import MultipeerConnectivity
// import GameKit
// GameKit不需要手动开启广播,它会在搜索控制器出现的时候自动互相广播搜索
let xmgSerType = "XMGsssss"
class ViewController: UIViewController {
let peerID : MCPeerID = MCPeerID(displayName: UIDevice.currentDevice().name)
lazy var session: MCSession = {
let session = MCSession(peer: self.peerID)
session.delegate = self
return session
}()
lazy var bvc: MCBrowserViewController = {
let bvc = MCBrowserViewController(serviceType: xmgSerType, session: self.session)
bvc.delegate = self
return bvc
}()
lazy var adv: MCAdvertiserAssistant = {
let info: [String: String] = ["title": "xmg117", "des": "summer is comming"]
let adv = MCAdvertiserAssistant(serviceType: xmgSerType, discoveryInfo: info, session: self.session)
return adv
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// GKPeerPickerController()
// session = MCSession(peer: self.peerID)
// session?.delegate = self
}
@IBOutlet weak var searchBluetoothPeripheral: UIButton!
@IBAction func searchBP(sender: AnyObject) {
self.presentViewController(self.bvc, animated: true) {
//
print("modal MCB")
}
}
@IBAction func switchAdv(sender: UISwitch) {
if sender.on == true {
adv.start()
print("adv start")
}else
{
adv.stop()
print("adv stop")
}
}
}
// 当协议的扩展中存在没有实现的非optional协议方法的时候,它就会报错,此时你只需要实现他们即可
extension ViewController: MCBrowserViewControllerDelegate
{
func browserViewControllerDidFinish(browserViewController: MCBrowserViewController)
{
print("browserViewControllerDidFinish")
}
// Notifies delegate that the user taps the cancel button.
func browserViewControllerWasCancelled(browserViewController: MCBrowserViewController)
{
print("browserViewControllerWasCancelled")
browserViewController.dismissViewControllerAnimated(true) {
print("dismissS")
}
}
func browserViewController(browserViewController: MCBrowserViewController, shouldPresentNearbyPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) -> Bool {
//
print("browserViewController")
if peerID == self.peerID {
print("It's the peer which I wanted!\(info)")
return true
}
return true
}
}
extension ViewController: MCSessionDelegate
{
func session(session: MCSession, peer peerID: MCPeerID, didChangeState state: MCSessionState)
{
print("\(state)")
print(state)
switch state {
case MCSessionState.Connected: break
//
case .Connecting: break
//
case .NotConnected: break
//
default: break
//
}
}
// Received data from remote peer.
func session(session: MCSession, didReceiveData data: NSData, fromPeer peerID: MCPeerID)
{
}
// Received a byte stream from remote peer.
func session(session: MCSession, didReceiveStream stream: NSInputStream, withName streamName: String, fromPeer peerID: MCPeerID)
{
}
// Start receiving a resource from remote peer.
func session(session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, withProgress progress: NSProgress)
{
}
// Finished receiving a resource from remote peer and saved the content
// in a temporary location - the app is responsible for moving the file
// to a permanent location within its sandbox.
func session(session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, atURL localURL: NSURL, withError error: NSError?)
{
}
}
在iOS7中,引入了一个全新的框架——Multipeer Connectivity(多点连接)。
利用Multipeer Connectivity框架,即使在没有连接到WiFi(WLAN)或移动网络(xG)
的情况下,距离较近的Apple设备(iMac/iPad/iPhone)之间可基于蓝牙和WiFi(P2P WiFi)
技术进行发现和连接实现近场通信。
Multipeer Connectivity扩充的功能与利用AirDrop传输文件非常类似,可以将其看作AirDrop不能直接使用的补偿,代价是需要自己实现。
手机不联网也能跟附近的人聊得火热的FireChat
和See You Around
等近场聊天App、近距离无网遥控交互拍照神器拍咯App
就是基于Multipeer Connectivity框架实现。
相比AirDrop,Multipeer Connectivity在进行发现和会话时并不要求同时打开WiFi和蓝牙,也不像AirDrop那样强制打开这两个开关,而是根据条件适时选择使用蓝牙或(和)WiFi。
粗略测试情况如下:
都未打开
:无法发现。蓝牙
:通过蓝牙发现和传输。双方都开启WiFi
:通过WiFi Direct发现和传输,速度接近AirDrop(Reliable速率稍低),不知道同一WLAN下是否优先走局域网?
双方都同时开启了WiFi和蓝牙
:应该是模拟AirDrop,通过低功耗蓝牙技术扫描发现握手,然后通过WiFi Direct传输。
类似sockaddr,用于标识连接的两端endpoint,通常是昵称或设备名称
。
该对象只开放了displayName属性,私有MCPeerIDInternal对象持有的设备相关的_idString/_pid64字段并未公开
。 在许多情况下,客户端同时广播并发现同一个服务,这将导致一些混乱,尤其是在client/server
模式中。所以,每一个服务都应有一个类型标示符——serviceType,它是由ASCII字母、数字和“-”组成的短文本串,最多15个字符
。
类似broadcaster,可以接收,并处理用户请求连接的响应。但是,这个类会有回调,告知有用户要与您的设备连接,然后可以自定义提示框,以及自定义连接处理
。
主线程(com.apple.main-thread(serial))创建MCNearbyServiceAdvertiser并启动startAdvertisingPeer。 MCNearbyServiceAdvertiserDelegate异步回调(didReceiveInvitationFromPeer)切换回主线程。 在主线程didReceiveInvitationFromPeer中创建MCSession并invitationHandler(YES, session)接受会话连接请求(accept参数为YES)。
类似servo listen+client connect,用于搜索附近的用户,并可以对搜索到的用户发出邀请加入某个会话中
。 主线程(com.apple.main-thread(serial))创建MCNearbyServiceBrowser并启动startBrowsingForPeers。 MCNearbyServiceBrowserDelegate异步回调(foundPeer/lostPeer)切换回主线程。 主线程创建MCSession并启动invitePeer。
启用和管理Multipeer连接会话中的所有人之间的沟通。 通过Sesion,给别人发送数据
注意,peerID并不具备设备识别属性。 类似TCP链接中的socket。创建MCSession时,需指定自身MCPeerID,类似bind。 为避免频繁的会话数据通知阻塞主线程,MCSessionDelegate异步回调(didChangeState/didReceiveCertificate/didReceiveData/didReceiveStream)有一个专门的回调线程——com.apple.MCSession.callbackQueue(serial)。为避免阻塞MCSeesion回调线程,最好新建数据读(写)线程!
MCAdvertiserAssistant //可以接收,并处理用户请求连接的响应。没有回调,会弹出默认的提示框,并处理连接。
MCBrowserViewController 弹出搜索框,需要手动modal
MCAdvertiserAssistant为针对Advertiser封装的管理助手;MCBrowserViewController继承自UIViewController,提供了基本的UI应用框架。 MCBrowser/MCAdvertiser的回调线程一般是delegate所在线程Queue:com.apple.main-thread(serial)。