iOS7新技術(shù):如何使用Multipeer Connectivity
Multipeer connectivity是一個(gè)使附近設(shè)備通過Wi-Fi網(wǎng)絡(luò)、P2P Wi-Fi以及藍(lán)牙個(gè)人局域網(wǎng)進(jìn)行通信的框架?;ハ噫溄拥墓?jié)點(diǎn)可以安全地傳遞信息、流或是其他文件資源,而不用通過網(wǎng)絡(luò)服務(wù)。
Advertising & Discovering
通信的第一步是讓大家互相知道彼此,我們通過廣播(Advertising)和發(fā)現(xiàn)(discovering)服務(wù)來實(shí)現(xiàn)。
廣播作為服務(wù)器搜索附近的節(jié)點(diǎn),而節(jié)點(diǎn)同時(shí)也去搜索附近的廣播。在許多情況下,客戶端同時(shí)廣播并發(fā)現(xiàn)同一個(gè)服務(wù),這將導(dǎo)致一些混亂,尤其是在client-server模式中。
所以,每一個(gè)服務(wù)都應(yīng)有一個(gè)類型(標(biāo)示符),它是由ASCII字母、數(shù)字和“-”組成的短文本串,最多15個(gè)字符。通常,一個(gè)服務(wù)的名字應(yīng)該由應(yīng)用程序的名字開始,后邊跟“-”和一個(gè)獨(dú)特的描述符號(hào)。(作者認(rèn)為這和 com.apple.*標(biāo)示符很像),就像下邊:
- static NSString * const XXServiceType = @"xx-service";
一個(gè)節(jié)點(diǎn)有一個(gè)唯一標(biāo)示MCPeerID對(duì)象,使用展示名稱進(jìn)行初始化,它可能是用戶指定的昵稱,或是單純的設(shè)備名稱。
- MCPeerID *localPeerID = [[MCPeerID alloc] initWithDisplayName:[[UIDevice currentDevice] name]];
節(jié)點(diǎn)使用NSNetService或者Bonjour C API進(jìn)行手動(dòng)廣播和發(fā)現(xiàn),但這是一個(gè)特別深入的問題,關(guān)于手動(dòng)節(jié)點(diǎn)管理可具體參見MCSession文檔。
Advertising
服務(wù)的廣播通過MCNearbyServiceAdvertiser來操作,初始化時(shí)帶著本地節(jié)點(diǎn)、服務(wù)類型以及任何可與發(fā)現(xiàn)該服務(wù)的節(jié)點(diǎn)進(jìn)行通信的可選信息。
發(fā)現(xiàn)信息使用Bonjour TXT records encoded(according to RFC 6763)發(fā)送。
- MCNearbyServiceAdvertiser *advertiser =
- [[MCNearbyServiceAdvertiser alloc] initWithPeer:localPeerID
- discoveryInfo:nil
- serviceType:XXServiceType];
- advertiser.delegate = self;
- [advertiser startAdvertisingPeer];
相關(guān)事件由advertiser的代理來處理,需遵從MCNearbyServiceAdvertiserDelegate協(xié)議。
在下例中,考慮到用戶可以選擇是否接受或拒絕傳入連接請(qǐng)求,并有權(quán)以拒絕或屏蔽任何來自該節(jié)點(diǎn)的后續(xù)請(qǐng)求選項(xiàng)。
- #pragma mark - MCNearbyServiceAdvertiserDelegate
- - (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser
- didReceiveInvitationFromPeer:(MCPeerID *)peerID
- withContext:(NSData *)context
- invitationHandler:(void(^)(BOOL accept, MCSession *session))invitationHandler
- {
- if ([self.mutableBlockedPeers containsObject:peerID]) {
- invitationHandler(NO, nil);
- return;
- }
- [[UIActionSheet actionSheetWithTitle:[NSString stringWithFormat:NSLocalizedString(@"Received Invitation from %@", @"Received Invitation from {Peer}"), peerID.displayName]
- cancelButtonTitle:NSLocalizedString(@"Reject", nil)
- destructiveButtonTitle:NSLocalizedString(@"Block", nil)
- otherButtonTitles:@[NSLocalizedString(@"Accept", nil)]
- block:^(UIActionSheet *actionSheet, NSInteger buttonIndex)
- {
- BOOL acceptedInvitation = (buttonIndex == [actionSheet firstOtherButtonIndex]);
- if (buttonIndex == [actionSheet destructiveButtonIndex]) {
- [self.mutableBlockedPeers addObject:peerID];
- }
- MCSession *session = [[MCSession alloc] initWithPeer:localPeerID
- securityIdentity:nil
- encryptionPreference:MCEncryptionNone];
- session.delegate = self;
- invitationHandler(acceptedInvitation, (acceptedInvitation ? session : nil));
- }] showInView:self.view];
- }
為了簡(jiǎn)單起見,本例中使用了一個(gè)帶有block的actionsheet來作為操作框,它可以直接給invitationHandler傳遞信 息,用以避免創(chuàng)建和管理delegate造成的過于凌亂的業(yè)務(wù)邏輯,以避免創(chuàng)建和管理自定義delegate object造成的過于凌亂的業(yè)務(wù)邏輯。這種方法可以用category來實(shí)現(xiàn),或者改編任何一個(gè)CocoaPods里有效的實(shí)現(xiàn)。
Creating a Session
在上面的例子中,我們創(chuàng)建了session,并在接受邀請(qǐng)連接時(shí)傳遞到節(jié)點(diǎn)。一個(gè)MCSession對(duì)象跟本地節(jié)點(diǎn)標(biāo)識(shí)符、securityIdentity以及encryptionPreference參數(shù)一起進(jìn)行初始化。
- MCSession *session = [[MCSession alloc] initWithPeer:localPeerID
- securityIdentity:nil
- encryptionPreference:MCEncryptionNone];
- session.delegate = self;
securityIdentity是一個(gè)可選參數(shù)。通過X.509證書,它允許節(jié)點(diǎn)安全識(shí)別并連接其他節(jié)點(diǎn)。當(dāng)設(shè)置了該參數(shù)時(shí),第一個(gè)對(duì)象應(yīng)該 是識(shí)別客戶端的SecIdentityRef,接著是一個(gè)或更多個(gè)用以核實(shí)本地節(jié)點(diǎn)身份的SecCertificateRef objects。
encryptionPreference參數(shù)指定是否加密節(jié)點(diǎn)之間的通信。MCEncryptionPreference枚舉提供的三種值是:
MCEncryptionOptional:會(huì)話更喜歡使用加密,但會(huì)接受未加密的連接。
MCEncryptionRequired:會(huì)話需要加密。
MCEncryptionNone:會(huì)話不應(yīng)該加密。
啟用加密會(huì)顯著降低傳輸速率,所以除非你的應(yīng)用程序很特別,需要對(duì)用戶敏感信息的處理,否則建議使用MCEncryptionNone。
MCSessionDelegate協(xié)議將會(huì)在發(fā)送和接受信息的部分被覆蓋.
Discovering
客戶端使用MCNearbyServiceBrowser來發(fā)現(xiàn)廣播,它需要local peer標(biāo)識(shí)符,以及非常類似MCNearbyServiceAdvertiser的服務(wù)類型來初始化:
- MCNearbyServiceBrowser *browser = [[MCNearbyServiceBrowser alloc] initWithPeer:localPeerID serviceType:XXServiceType];
- browser.delegate = self;
可能會(huì)有很多節(jié)點(diǎn)廣播一個(gè)特定的服務(wù),所以為了方便用戶(或開發(fā)者),MCBrowserViewController將提供一個(gè)內(nèi)置的、標(biāo)準(zhǔn)的方式來呈現(xiàn)鏈接到廣播節(jié)點(diǎn):
- MCBrowserViewController *browserViewController =
- [[MCBrowserViewController alloc] initWithBrowser:browser
- session:session];
- browserViewController.delegate = self;
- [self presentViewController:browserViewController
- animated:YES
- completion:
- ^{
- [browser startBrowsingForPeers];
- }];
當(dāng)browser完成節(jié)點(diǎn)連接后,它將使用它的delegate調(diào)用browserViewControllerDidFinish:,以通知展示視圖控制器--它應(yīng)該更新UI以適應(yīng)新連接的客戶端。
Sending & Receiving Information
一旦節(jié)點(diǎn)彼此相連,它們將能互傳信息。Multipeer Connectivity框架區(qū)分三種不同形式的數(shù)據(jù)傳輸:
Messages是定義明確的信息,比如端文本或者小序列化對(duì)象。
Streams 流是可連續(xù)傳輸數(shù)據(jù)(如音頻,視頻或?qū)崟r(shí)傳感器事件)的信息公開渠道。
Resources是圖片、電影以及文檔的文件。
Messages
Messages使用-sendData:toPeers:withMode:error::方法發(fā)送。
- NSString *message = @"Hello, World!";
- NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];
- NSError *error = nil;
- if (![self.session sendData:data
- toPeers:peers
- withMode:MCSessionSendDataReliable
- error:&error]) {
- NSLog(@"[Error] %@", error);
- }
通過MCSessionDelegate方法 -sessionDidReceiveData:fromPeer:收取信息。以下是如何解碼先前示例代碼中發(fā)送的消息:
- #pragma mark - MCSessionDelegate
- - (void)session:(MCSession *)session
- didReceiveData:(NSData *)data
- fromPeer:(MCPeerID *)peerID
- {
- NSString *message =
- [[NSString alloc] initWithData:data
- encoding:NSUTF8StringEncoding];
- NSLog(@"%@", message);
- }
另一種方法是發(fā)送NSKeyedArchiver編碼的對(duì)象:
- id <NSSecureCoding> object = // ...;
- NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object];
- NSError *error = nil;
- if (![self.session sendData:data
- toPeers:peers
- withMode:MCSessionSendDataReliable
- error:&error]) {
- NSLog(@"[Error] %@", error);
- }
- #pragma mark - MCSessionDelegate
- - (void)session:(MCSession *)session
- didReceiveData:(NSData *)data
- fromPeer:(MCPeerID *)peerID
- {
- NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
- unarchiver.requiresSecureCoding = YES;
- id object = [unarchiver decodeObject];
- [unarchiver finishDecoding];
- NSLog(@"%@", object);
- }
為了防范對(duì)象替換攻擊,設(shè)置requiresSecureCoding為YES是很重要的,這樣如果根對(duì)象類沒有遵從<NSSecureCoding>,就會(huì)拋出一個(gè)異常。欲了解更多信息,請(qǐng)參閱[NSHipster article on NSSecureCoding]。
Streams
Streams 使用 -startStreamWithName:toPeer:創(chuàng)建:
- NSOutputStream *outputStream =
- [session startStreamWithName:name
- toPeer:peer];
- stream.delegate = self;
- [stream scheduleInRunLoop:[NSRunLoop mainRunLoop]
- forMode:NSDefaultRunLoopMode];
- [stream open];
- // ...
Streams通過MCSessionDelegate的方法session:didReceiveStream:withName:fromPeer:來接收:
- #pragma mark - MCSessionDelegate
- - (void)session:(MCSession *)session
- didReceiveStream:(NSInputStream *)stream
- withName:(NSString *)streamName
- fromPeer:(MCPeerID *)peerID
- {
- stream.delegate = self;
- [stream scheduleInRunLoop:[NSRunLoop mainRunLoop]
- forMode:NSDefaultRunLoopMode];
- [stream open];
- }
輸入和輸出的streams必須安排好并打開,然后才能使用它們。一旦這樣做,streams就可以被讀出和寫入。
Resources
Resources 發(fā)送使用 -sendResourceAtURL:withName:toPeer:withCompletionHandler::
- NSURL *fileURL = [NSURL fileURLWithPath:@"path/to/resource"];
- NSProgress *progress =
- [self.session sendResourceAtURL:fileURL
- withName:[fileURL lastPathComponent]
- toPeer:peer
- withCompletionHandler:^(NSError *error)
- {
- NSLog(@"[Error] %@", error);
- }];
返回的NSProgress對(duì)象可以是通過KVO(Key-Value Observed)來監(jiān)視文件傳輸?shù)倪M(jìn)度,并且它提供取消傳輸?shù)姆椒ǎ?cancel。
接收資源實(shí)現(xiàn)MCSessionDelegate兩種方 法:-session:didStartReceivingResourceWithName:fromPeer:withProgress: 和 -session:didFinishReceivingResourceWithName:fromPeer:atURL:withError:
- #pragma mark - MCSessionDelegate
- - (void)session:(MCSession *)session
- didStartReceivingResourceWithName:(NSString *)resourceName
- fromPeer:(MCPeerID *)peerID
- withProgress:(NSProgress *)progress
- {
- // ...
- }
- - (void)session:(MCSession *)session
- didFinishReceivingResourceWithName:(NSString *)resourceName
- fromPeer:(MCPeerID *)peerID
- atURL:(NSURL *)localURL
- withError:(NSError *)error
- {
- NSURL *destinationURL = [NSURL fileURLWithPath:@"/path/to/destination"];
- NSError *error = nil;
- if (![[NSFileManager defaultManager] moveItemAtURL:localURL
- toURL:destinationURL
- error:&error]) {
- NSLog(@"[Error] %@", error);
- }
- }
再次說明,在傳輸期間NSProgress parameter in -session:didStartReceivingResourceWithName:fromPeer:withProgress:允許接收節(jié)點(diǎn)來 監(jiān)控文件傳輸進(jìn)度。在 -session:didFinishReceivingResourceWithName:fromPeer:atURL:withError: 中,delegate的責(zé)任是從臨時(shí)localURL移動(dòng)文件至永久位置。
Multipeer是突破性的API,其價(jià)值才剛剛開始被理解。雖然完整的支持功能比如AirDrop目前僅限于最新的設(shè)備,你應(yīng)該會(huì)看到它將成為讓所有人盼望的功能。
本文由郭歷成[博客]翻譯自nshipster中的Multipeer Connectivity一節(jié)。
【移動(dòng)開發(fā)視頻課程推薦】
- iOS培訓(xùn)之Objective-C基礎(chǔ)視頻教程(40集)
- Cocos2d-x從零開始【5天掌握跨平臺(tái)游戲開發(fā)利器】(12集)
- Objective C編程基礎(chǔ)(24集)
- Android技術(shù)輕松入門課程(12集)
- 微信開放平臺(tái)-Android應(yīng)用接入(4集)
- Cocos2d-x跨平臺(tái)游戲開發(fā)入門基礎(chǔ)(29集)
- iOS開發(fā)視頻教程-iOS網(wǎng)絡(luò)編程【高級(jí)篇】(39集)
- 移動(dòng)應(yīng)用用戶體驗(yàn)設(shè)計(jì)高級(jí)課程(60集)
- 從零學(xué)習(xí)iOS開發(fā)–UI多視圖(30集)
- iOS開發(fā)視頻教程【基礎(chǔ)入門篇】