マルチピアコネクティビティは、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マルチビュー