iOS7將GameKit中的藍(lán)牙模塊單獨(dú)出的一個(gè)Multipeer Connectivity Framework佑力,通過(guò)發(fā)現(xiàn)附近的設(shè)備用wifi或藍(lán)牙進(jìn)行p2p連接底桂。
概念和Class結(jié)構(gòu)
- session植袍,比如一個(gè)共同的聊天室就可以稱為一個(gè)session,class為
MCSession
- peerID籽懦,
MCPeerID
代表一個(gè)session內(nèi)的設(shè)備ID于个,類似聊天室的ID號(hào) - Advertiser,告訴附近的設(shè)備可以加入自己的session進(jìn)行通訊猫十,class有兩個(gè)览濒,
MCAdvertiserAssistant
是自動(dòng)的Adertiser管理類呆盖,另一個(gè)是MCNearbyServiceAdvertiser
需要自己實(shí)現(xiàn)代理方法 - Browser,尋找并加入附近的Advertiser贷笛,類似Advertiser也有兩個(gè)class应又,
MCNearbyServiceBrowser
同理MCNearbyServiceAdvertiser
,MCBrowserViewController
類似MCAdvertiserAssistant
是一個(gè)有默認(rèn)視圖的browser乏苦。
實(shí)現(xiàn)multipeer有兩種方法株扛,比如官方例子使用的是MCAdvertiserAssistant
和MCBrowserViewController
的組合,這種方法的優(yōu)點(diǎn)是簡(jiǎn)單汇荐,缺點(diǎn)是自帶的UI太丑洞就,也不好明白具體的流程,所以本文選擇自己實(shí)現(xiàn)代理的方法掀淘。
session里peer可以同時(shí)是advertiser和browser旬蟋,比如聊天室,每個(gè)人都可以發(fā)起或者加入革娄。但peer也可以分開做advertiser和browser倾贰,這就類似client和server的模式。由于將advertiser和browser寫在一個(gè)工程會(huì)造成混淆拦惋,所以我選擇第二種模式來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的發(fā)送照片的demo匆浙,一個(gè)設(shè)備作為發(fā)送者拍照之后發(fā)送給session里的其他用戶。
Adertiser
我決定將發(fā)送者作為Adertiser來(lái)實(shí)現(xiàn)厕妖,稱為Server首尼。其實(shí)作為browser也是可以的。首先創(chuàng)建一個(gè)single view的工程言秸,然后在storyboard里加入一個(gè)顯示連接狀態(tài)的status
label软能,再加入一個(gè)發(fā)送圖片的button,并加入對(duì)應(yīng)的IBOutlet(見本文最后的代碼)井仰。
完成UI部分之后埋嵌,首先需要import frameworkimport MultipeerConnectivity
,然后建立Adertiser俱恶。
let serviceType: String = "bs-mc"
var peerID: MCPeerID!
var session: MCSession!
var advertiser: MCNearbyServiceAdvertiser!
let displayName = UIDevice.currentDevice().name
self.peerID = MCPeerID(displayName: displayName)
self.session = MCSession(peer: self.peerID)
self.session.delegate = self
self.advertiser = MCNearbyServiceAdvertiser(peer: self.peerID, discoveryInfo: nil, serviceType: self.serviceType)
self.advertiser.delegate = self
self.advertiser.startAdvertisingPeer()
serviceType
用來(lái)標(biāo)識(shí)哪些人能加入session,這個(gè)字符串不能超過(guò)15個(gè)字符范舀,橫桿最多只能有一個(gè)合是。當(dāng)有browser申請(qǐng)加入時(shí)就會(huì)調(diào)用下面的代理方法。
func advertiser(advertiser: MCNearbyServiceAdvertiser!, didReceiveInvitationFromPeer peerID:MCPeerID!, withContext context: NSData!, invitationHandler: ((Bool, MCSession!) -> Void)!) {
println("advertiser receive invitation from peer \(peerID.displayName)")
let alertController = UIAlertController(title: "Permission", message: "\(peerID.displayName) wants to know your position", preferredStyle: .Alert)
let okAction = UIAlertAction(title: "OK", style: .Default) { (action) -> Void in
invitationHandler(true, self.session)
}
let noAction = UIAlertAction(title: "NO", style: .Cancel) { (action) -> Void in
invitationHandler(false, nil)
}
alertController.addAction(okAction)
alertController.addAction(noAction)
self.presentViewController(alertController, animated: true, completion: nil)
}
當(dāng)有browser申請(qǐng)加入session時(shí)該方法調(diào)用锭环,這里會(huì)彈出一個(gè)alert聪全,根據(jù)選擇來(lái)回調(diào)invitationHandler
并傳入對(duì)應(yīng)的值,這樣browser就會(huì)接收到同意或拒絕辅辩。
MCSessionDelegate
的很多方法都是required难礼,但這里只使用了session(_:peer:didChangeState:)
用于改變status
label娃圆。
func session(session: MCSession!, peer peerID: MCPeerID!, didChangeState state: MCSessionState) {
switch state {
case .Connected:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.status.text = "connected"
})
case .Connecting:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.status.text = "connecting..."
})
case .NotConnected:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.status.text = "connect fail"
})
default:
println("error")
}
}
Browser
圖片的接受者作為Browser去發(fā)現(xiàn)Advertiser的session,稱為Client蛾茉。跟創(chuàng)建Advertiser一樣讼呢,Browser也需要peerID、session谦炬。
let serviceType: String = "bs-mc"
var peerID: MCPeerID!
var session: MCSession!
var browser: MCNearbyServiceBrowser!
let displayName = UIDevice.currentDevice().name
self.peerID = MCPeerID(displayName: displayName)
self.session = MCSession(peer: self.peerID)
self.session.delegate = self
self.browser = MCNearbyServiceBrowser(peer: self.peerID, serviceType: self.serviceType)
self.browser.delegate = self
self.browser.startBrowsingForPeers()
serviceType
需要跟advertiser相同悦屏,當(dāng)Browser發(fā)現(xiàn)附近的advertiser之后就會(huì)調(diào)用方法browser(_:foundPeer:withDiscoveryInfo:)
。
func browser(browser: MCNearbyServiceBrowser!, foundPeer peerID: MCPeerID!, withDiscoveryInfo info: [NSObject : AnyObject]!) {
println("browser found peear \(peerID.displayName)")
browser.invitePeer(peerID, toSession: self.session, withContext: nil, timeout: 0)
}
iOS8之后browser會(huì)在找到advertiser之后自動(dòng)申請(qǐng)加入session键思,可以去掉browser.invitePeer
础爬,詳見Choosing an inviter when using Multipeer Connectivity。
數(shù)據(jù)傳輸
當(dāng)Server和Client建立起連接之后就可以互相發(fā)送數(shù)據(jù)了吼鳞。首先Server拍照看蚜,然后將照片作為數(shù)據(jù)發(fā)送出去。
func imagePickerController(picker: UIImagePickerController!, didFinishPickingImage image: UIImage!, editingInfo: [NSObject : AnyObject]!) {
picker.dismissViewControllerAnimated(true, completion: nil)
if let data = UIImagePNGRepresentation(image) {
var error: NSError?
self.session.sendData(data, toPeers: self.session.connectedPeers, withMode: .Unreliable,
error: &error)
}
}
當(dāng)Client接受到數(shù)據(jù)后就會(huì)調(diào)用方法session(_:didReceiveData:fromPeer:)
赔桌。
func session(session: MCSession!, didReceiveData data: NSData!, fromPeer peerID: MCPeerID!) {
if let image = UIImage(data: data) {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.imageView.image = image
})
}
}
類似的也可以發(fā)送流數(shù)據(jù)和資源文件失乾,startStreamWithName
和sendResourceAtURL
。
Demo代碼已放到Github:MultipeerConnectivityDemo