iOS7新技術(shù):如何使用Multipeer Connectivity
Multipeer connectivity是一個使附近設(shè)備通過Wi-Fi網(wǎng)絡(luò)、P2P Wi-Fi以及藍(lán)牙個人局域網(wǎng)進行通信的框架?;ハ噫溄拥墓?jié)點可以安全地傳遞信息、流或是其他文件資源,而不用通過網(wǎng)絡(luò)服務(wù)。
Advertising & Discovering
通信的第一步是讓大家互相知道彼此,我們通過廣播(Advertising)和發(fā)現(xiàn)(discovering)服務(wù)來實現(xiàn)。
廣播作為服務(wù)器搜索附近的節(jié)點,而節(jié)點同時也去搜索附近的廣播。在許多情況下,客戶端同時廣播并發(fā)現(xiàn)同一個服務(wù),這將導(dǎo)致一些混亂,尤其是在client-server模式中。
所以,每一個服務(wù)都應(yīng)有一個類型(標(biāo)示符),它是由ASCII字母、數(shù)字和“-”組成的短文本串,最多15個字符。通常,一個服務(wù)的名字應(yīng)該由應(yīng)用程序的名字開始,后邊跟“-”和一個獨特的描述符號。(作者認(rèn)為這和 com.apple.*標(biāo)示符很像),就像下邊:
- static NSString * const XXServiceType = @"xx-service";
 
一個節(jié)點有一個唯一標(biāo)示MCPeerID對象,使用展示名稱進行初始化,它可能是用戶指定的昵稱,或是單純的設(shè)備名稱。
- MCPeerID *localPeerID = [[MCPeerID alloc] initWithDisplayName:[[UIDevice currentDevice] name]];
 
節(jié)點使用NSNetService或者Bonjour C API進行手動廣播和發(fā)現(xiàn),但這是一個特別深入的問題,關(guān)于手動節(jié)點管理可具體參見MCSession文檔。
Advertising
服務(wù)的廣播通過MCNearbyServiceAdvertiser來操作,初始化時帶著本地節(jié)點、服務(wù)類型以及任何可與發(fā)現(xiàn)該服務(wù)的節(jié)點進行通信的可選信息。
發(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é)議。
在下例中,考慮到用戶可以選擇是否接受或拒絕傳入連接請求,并有權(quán)以拒絕或屏蔽任何來自該節(jié)點的后續(xù)請求選項。
- #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];
 - }
 
為了簡單起見,本例中使用了一個帶有block的actionsheet來作為操作框,它可以直接給invitationHandler傳遞信 息,用以避免創(chuàng)建和管理delegate造成的過于凌亂的業(yè)務(wù)邏輯,以避免創(chuàng)建和管理自定義delegate object造成的過于凌亂的業(yè)務(wù)邏輯。這種方法可以用category來實現(xiàn),或者改編任何一個CocoaPods里有效的實現(xiàn)。
Creating a Session
在上面的例子中,我們創(chuàng)建了session,并在接受邀請連接時傳遞到節(jié)點。一個MCSession對象跟本地節(jié)點標(biāo)識符、securityIdentity以及encryptionPreference參數(shù)一起進行初始化。
- MCSession *session = [[MCSession alloc] initWithPeer:localPeerID
 - securityIdentity:nil
 - encryptionPreference:MCEncryptionNone];
 - session.delegate = self;
 
securityIdentity是一個可選參數(shù)。通過X.509證書,它允許節(jié)點安全識別并連接其他節(jié)點。當(dāng)設(shè)置了該參數(shù)時,第一個對象應(yīng)該 是識別客戶端的SecIdentityRef,接著是一個或更多個用以核實本地節(jié)點身份的SecCertificateRef objects。
encryptionPreference參數(shù)指定是否加密節(jié)點之間的通信。MCEncryptionPreference枚舉提供的三種值是:
MCEncryptionOptional:會話更喜歡使用加密,但會接受未加密的連接。
MCEncryptionRequired:會話需要加密。
MCEncryptionNone:會話不應(yīng)該加密。
啟用加密會顯著降低傳輸速率,所以除非你的應(yīng)用程序很特別,需要對用戶敏感信息的處理,否則建議使用MCEncryptionNone。
MCSessionDelegate協(xié)議將會在發(fā)送和接受信息的部分被覆蓋.
Discovering
客戶端使用MCNearbyServiceBrowser來發(fā)現(xiàn)廣播,它需要local peer標(biāo)識符,以及非常類似MCNearbyServiceAdvertiser的服務(wù)類型來初始化:
- MCNearbyServiceBrowser *browser = [[MCNearbyServiceBrowser alloc] initWithPeer:localPeerID serviceType:XXServiceType];
 - browser.delegate = self;
 
可能會有很多節(jié)點廣播一個特定的服務(wù),所以為了方便用戶(或開發(fā)者),MCBrowserViewController將提供一個內(nèi)置的、標(biāo)準(zhǔn)的方式來呈現(xiàn)鏈接到廣播節(jié)點:
- MCBrowserViewController *browserViewController =
 - [[MCBrowserViewController alloc] initWithBrowser:browser
 - session:session];
 - browserViewController.delegate = self;
 - [self presentViewController:browserViewController
 - animated:YES
 - completion:
 - ^{
 - [browser startBrowsingForPeers];
 - }];
 
當(dāng)browser完成節(jié)點連接后,它將使用它的delegate調(diào)用browserViewControllerDidFinish:,以通知展示視圖控制器--它應(yīng)該更新UI以適應(yīng)新連接的客戶端。
Sending & Receiving Information
一旦節(jié)點彼此相連,它們將能互傳信息。Multipeer Connectivity框架區(qū)分三種不同形式的數(shù)據(jù)傳輸:
Messages是定義明確的信息,比如端文本或者小序列化對象。
Streams 流是可連續(xù)傳輸數(shù)據(jù)(如音頻,視頻或?qū)崟r傳感器事件)的信息公開渠道。
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編碼的對象:
- 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);
 - }
 
為了防范對象替換攻擊,設(shè)置requiresSecureCoding為YES是很重要的,這樣如果根對象類沒有遵從<NSSecureCoding>,就會拋出一個異常。欲了解更多信息,請參閱[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對象可以是通過KVO(Key-Value Observed)來監(jiān)視文件傳輸?shù)倪M度,并且它提供取消傳輸?shù)姆椒ǎ?cancel。
接收資源實現(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é)點來 監(jiān)控文件傳輸進度。在 -session:didFinishReceivingResourceWithName:fromPeer:atURL:withError: 中,delegate的責(zé)任是從臨時localURL移動文件至永久位置。
Multipeer是突破性的API,其價值才剛剛開始被理解。雖然完整的支持功能比如AirDrop目前僅限于最新的設(shè)備,你應(yīng)該會看到它將成為讓所有人盼望的功能。
本文由郭歷成[博客]翻譯自nshipster中的Multipeer Connectivity一節(jié)。
【移動開發(fā)視頻課程推薦】
- iOS培訓(xùn)之Objective-C基礎(chǔ)視頻教程(40集)
 - Cocos2d-x從零開始【5天掌握跨平臺游戲開發(fā)利器】(12集)
 - Objective C編程基礎(chǔ)(24集)
 - Android技術(shù)輕松入門課程(12集)
 - 微信開放平臺-Android應(yīng)用接入(4集)
 - Cocos2d-x跨平臺游戲開發(fā)入門基礎(chǔ)(29集)
 - iOS開發(fā)視頻教程-iOS網(wǎng)絡(luò)編程【高級篇】(39集)
 - 移動應(yīng)用用戶體驗設(shè)計高級課程(60集)
 - 從零學(xué)習(xí)iOS開發(fā)–UI多視圖(30集)
 - iOS開發(fā)視頻教程【基礎(chǔ)入門篇】
 















 
 
 


 
 
 
 