blog

iOS 7の新機能:マルチピア接続の使い方

Multipeerは、近くにあるデバイスがWi-Fiネットワーク、P2P Wi-Fi、BluetoothパーソナルLANを介して通信できるようにするフレームワークです。相互リンクされたノードは、ウェブ...

May 21, 2014 · 14 min. read
シェア

マルチピアコネクティビティは、Wi-Fiネットワーク、P2P Wi-Fi、BluetoothパーソナルLANを介して、近くにあるデバイスの通信を可能にするフレームワークです。相互リンクされたノードは、ウェブサービスを経由することなく、メッセージ、ストリーム、その他のファイルリソースを安全に渡すことができます。

広告と発見

コミュニケーションの第一歩は、ブロードキャストやディスカバリー・サービスを通じて、みんなにお互いのことを知らせることです。

ブロードキャストは、サーバーが近くのノードを検索し、サーバーも同時に近くのブロードキャストを検索します。多くの場合、クライアントはブロードキャストと同時に同じサービスを発見します。

これはASCII文字、数字、"-"からなる短いテキスト文字列で、最大15文字です。通常、サービス名はアプリケーション名で始まり、"-"とユニークな記述子が続きます。以下のような感じです:

static NSString * const XXServiceType = @"xx-service"; 

ノードには一意にラベル付けされた MCPeerID オブジェクトがあり、表示名(ユーザが割り当てたニックネームまたは単にデバイスの名前)を使って初期化されます。

MCPeerID *localPeerID = [[MCPeerID alloc] initWithDisplayName:[[UIDevice currentDevice] name]]; 

ノードはNSNetServiceまたはBonjour C APIを使用して手動でブロードキャストおよび検出されますが、これは特に詳細な問題であり、手動ノード管理については特にMCSessionのドキュメントを参照してください。

広告

サービスのブロードキャストは、MCNearbyServiceAdvertiserを通じて行われます。MCNearbyServiceAdvertiserは、ローカルノード、サービスのタイプ、およびサービスを発見したノードと通信するために使用できるオプション情報で初期化されます。

ディスカバリー・メッセージは、Bonjour TXTレコードをエンコードして送信されます。

MCNearbyServiceAdvertiser *advertiser = 
    [[MCNearbyServiceAdvertiser alloc] initWithPeer:localPeerID 
                                      discoveryInfo:nil 
                                        serviceType:XXServiceType]; 
advertiser.delegate = self; 
[advertiser startAdvertisingPeer]; 

以下の例では、ユーザーは着信接続要求を受け入れるか拒否するかを選択でき、そのノードからの後続の要求オプションを拒否またはブロックする権利を持つことが想定されます。

#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]; 
} 

簡単にするために、この例ではアクションボックスとしてブロックを持つアクションシートを使用しています。invitationHandlerに直接情報を渡すことができるので、ビジネスロジックがごちゃごちゃになりすぎるデリゲートの作成と管理を避けるために、カスタムデリゲートオブジェクトの作成と管理を避けるために、ビジネスロジックがごちゃごちゃになりすぎるデリゲートオブジェクトの作成と管理を避けるために、invitationHandlerに直接情報を渡すことができます。カスタムデリゲートオブジェクトの作成と管理によってビジネスロジックが多くなりすぎるのを避けるためです。このアプローチは、カテゴリを使用して実装することもできますし、RFC 6763いずれかを適用することもできます。

セッションの作成

上記の例では、接続の招待が受け入れられたときにセッションが作成され、ノードに渡されます。MCSession オブジェクトは、ローカル・ノード識別子、securityIdentity、および encryptionPreference パラメータとともに初期化されます。

MCSession *session = [[MCSession alloc] initWithPeer:localPeerID 
                                    securityIdentity:nil 
                                encryptionPreference:MCEncryptionNone]; 
session.delegate = self; 

securityIdentity はオプションのパラメータです。これにより、ノードは、X.509 証明書を介して他のノードを安全に識別し、接続することができます。このパラメータが設定されている場合、最初のオブジェクトはクライアントを識別する SecIdentityRef で、その後にローカルノードの ID を検証する 1 つ以上の SecCertificateRef オブジェクトが続きます。

MCEncryptionOptional: セッションは暗号化を好みますが、暗号化されていない接続も受け入れます。

MCEncryptionRequired:セッションには暗号化が必要です。

MCEncryptionNone:セッションは暗号化されるべきではありません。

暗号化を有効にすると転送速度が大幅に低下するため、非常に特殊なアプリケーションで機密性の高いユーザー情報を扱う必要がある場合を除き、MCEncryptionNoneを使用することをお勧めします。

MCSessionDelegate プロトコルは、メッセージの送受信のためにオーバーライドされます。

発見

MCNearbyServiceBrowser *browser = [[MCNearbyServiceBrowser alloc] initWithPeer:localPeerID serviceType:XXServiceType]; 
browser.delegate = self; 

特定のサービスをブロードキャストするノードが多数存在する可能性があるため、ユーザーの利便性のために、MCBrowserViewControllerは、ブロードキャストノードへのリンクをレンダリングする組み込みの標準的な方法を提供します:

MCBrowserViewController *browserViewController = 
    [[MCBrowserViewController alloc] initWithBrowser:browser 
                                             session:session]; 
browserViewController.delegate = self; 
[self presentViewController:browserViewController 
                   animated:YES 
                 completion: 
^{ 
    [browser startBrowsingForPeers]; 
}]; 

ブラウザがノードへの接続を終了すると、デリゲートを使用して browserViewControllerDidFinish: を呼び出し、表示ビューコントローラに通知します。

情報の送受信

マルチピア接続フレームワークでは、3つの異なるデータ伝送形態を区別しています:

メッセージとは、エンド・テキストや小さな直列化オブジェクトのような、明確に定義されたメッセージのことです。

ストリーム ストリームは、データの継続的な送信を可能にする情報の公開チャネルです。

リソースとは、写真、フィルム、文書のファイルです。

メッセージ

メッセージは、-sendData:toPeers:withMode:error::メソッドを使用して送信されます。

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:で受信されます。先ほどのサンプルコードで送信されたメッセージをデコードする方法を説明します:

#pragma mark - MCSessionDelegate 
  
- (void)session:(MCSession *)session 
 didReceiveData:(NSData *)data 
       fromPeer:(MCPeerID *)peerID 
{ 
    NSString *message = 
        [[NSString alloc] initWithData:data 
                              encoding:NSUTF8StringEncoding]; 
    NSLog(@"%@", message); 
} 

もう1つの方法は、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); 
} 

オブジェクト置換攻撃から保護するためには、requiresSecureCoding を YES に設定し、ルート・オブジェクト・クラスが <NSSecureCoding> に準拠していない場合に例外がスローされるようにすることが重要です。詳細は個人CocoaPods効果的な実装] を参照してください。

ストリーム

ストリームは -startStreamWithName:toPeer: で作成されます:

NSOutputStream *outputStream = 
    [session startStreamWithName:name 
                          toPeer:peer]; 
 
stream.delegate = self; 
[stream scheduleInRunLoop:[NSRunLoop mainRunLoop] 
                forMode:NSDefaultRunLoopMode]; 
[stream open]; 
 
// ... 

ストリームは、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]; 
} 

入出力ストリームは、使用する前に配置し、オープンする必要があります。これが完了すると、ストリームの読み書きが可能になります。

リソース

リソース -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オブジェクトは、ファイル転送の進行状況を監視するために使用することができ、転送をキャンセルするためのメソッドを提供します:-cancel。

リソースを受け取るための MCSessionDelegate を実装するには、次の 2 つの方法があります: -session: didStartReceivingResourceWithName:fromPeer:withProgress: と -session: didFinishReceivingResourceWithName:fromPeer:atURL:withError: と -session: didFinishReceivingResourceWithName:fromPeer:atURL:withError: です。didFinishReceivingResourceWithName:fromPeer:atURL:withError:および -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); 
    } 
} 

Multipeerは、その価値が理解され始めたばかりの画期的なAPIです。AirDropのような機能の完全なサポートは、現在のところ最新のデバイスに限られていますが、将来的には誰もが待ち望む機能になるはずです。

  • Objective Cプログラミングの基礎
  • アンドロイド入門コース
  • Cocos2d-xクロスプラットフォームゲーム開発入門基礎講座
  • モバイルアプリケーションのためのUXデザイン 上級コース
  • ゼロから学ぶiOS開発 - UIマルチビュー
Read next

LTEネットワークは完全なESCの時代へ

LTE時代、無線ネットワークにはいくつかの特徴があります:2G、3G、4Gネットワークが同時に稼動し、ネットワークの厚みが増し、屋外マクロ局、屋内小型局、マイクロ局などが3次元カバレッジネットワークを形成し、ネットワークの密度が増し、マルチネットワーク連携とネットワーク最適化が高性能LTEネットワーク構築の鍵となります。そのため、運用

May 20, 2014 · 4 min read