web-dev-qa-db-ja.com

MCSessionピアがランダムに切断されるのはなぜですか?

MCNearbyServiceBrowserとMCNearbyServiceAdvertiserを使用して、2つのピアをMCSessionに参加させます。 MCSessionのsendDataメソッドを使用して、それらの間でデータを送信できます。セッションのMCSessionDelegate didChangeStateハンドラーを介してMCSessionStateNotConnectedをランダムに(私が制御するイベントのためではなく)受け取るまで、すべてが期待どおりに機能しているようです。さらに、MCSessionのconnectedPeers配列にピアがなくなりました。

2つの質問:なぜですか? MCSessionが切断されないようにするにはどうすればよいですか?

41
tillerstarr

これはバグで、Appleに報告しました。ドキュメントはdidReceiveCertificateコールバックはオプションであると主張していますが、そうではありません。このメソッドをMCSessionDelegateに追加します。

- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
 {
     certificateHandler(YES);
 }

ランダムな切断は停止するはずです。

26
Andrew Cone

[〜#〜] update [〜#〜] Appleへのサポートチケットを使用した後、sendDataの呼び出しが多すぎ、データが多すぎると切断が発生する可能性があることを確認しました。

ブレークポイントに到達したときとバックグラウンド化したときに切断されました。アプリストアではブレークポイントが発生しないため、アプリがバックグラウンドに入る直前にバックグラウンドタスクを開始して、バックグラウンドケースを処理する必要があります。次に、アプリがフォアグラウンドに戻ったら、このタスクを終了します。 iOS 7では、バックグラウンドで約3分かかるため、何もないよりはましです。

追加の戦略は、[[UIApplication sharedApplication] backgroundTimeRemaining]を使用して、バックグラウンドタイムが切れる15秒前にローカル通知をスケジュールすることです。これにより、ユーザーを中断してマルチピアフレームワークをシャットダウンする必要がある前に、ユーザーをアプリに戻すことができます。 。おそらくローカル通知は、セッションが10秒程度で期限切れになることを警告します...

バックグラウンドタスクの期限が切れ、アプリがまだバックグラウンドにある場合、マルチピア接続に関連するすべてのものを破棄する必要があります。そうしないと、クラッシュします。

- (void) createExpireNotification
{
    [self killExpireNotification];

    if (self.connectedPeerCount != 0) // if peers connected, setup kill switch
    {
        NSTimeInterval gracePeriod = 20.0f;

        // create notification that will get the user back into the app when the background process time is about to expire
        NSTimeInterval msgTime = UIApplication.sharedApplication.backgroundTimeRemaining - gracePeriod;
        UILocalNotification* n = [[UILocalNotification alloc] init];
        self.expireNotification = n;
        self.expireNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:msgTime];
        self.expireNotification.alertBody = TR(@"Text_MultiPeerIsAboutToExpire");
        self.expireNotification.soundName = UILocalNotificationDefaultSoundName;
        self.expireNotification.applicationIconBadgeNumber = 1;

        [UIApplication.sharedApplication scheduleLocalNotification:self.expireNotification];
    }
}

- (void) killExpireNotification
{
    if (self.expireNotification != nil)
    {
        [UIApplication.sharedApplication cancelLocalNotification:self.expireNotification];
        self.expireNotification = nil;
    }
}

- (void) applicationWillEnterBackground
{
    self.taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
    {
        [self shutdownMultiPeerStuff];
        [[UIApplication sharedApplication] endBackgroundTask:self.taskId];
        self.taskId = UIBackgroundTaskInvalid;
    }];
    [self createExpireNotification];
}

- (void) applicationWillEnterForeground
{
    [self killExpireNotification];
    if (self.taskId != UIBackgroundTaskInvalid)
    {
        [[UIApplication sharedApplication] endBackgroundTask:self.taskId];
        self.taskId = UIBackgroundTaskInvalid;
    }
}

- (void) applicationWillTerminate
{
    [self killExpireNotification];
    [self stop]; // shutdown multi-peer
}

Appleバグのため、MCSessionデリゲートにもこのハンドラが必要です。

- (void) session:(MCSession*)session didReceiveCertificate:(NSArray*)certificate fromPeer:(MCPeerID*)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
 {
     if (certificateHandler != nil) { certificateHandler(YES); }
 }
17
jjxtra

これには多くの原因があり、これまでの2つの答えはどちらも私の経験では正しいです。他の同様の質問で見つけられるもう1つはこれです:1つのピアのみが他の招待を受け入れることができます

つまり、すべてのデバイスが広告主とブラウザの両方であるアプリを設定した場合、どのデバイスでも、発見された他のユーザーを自由に招待してセッションに参加させることができます。ただし、任意の2つのデバイス間で、実際に招待を受け入れて他のデバイスに接続できるのは1つのデバイスのみです。両方のデバイスがお互いの招待を受け入れると、1分以内に切断されます。

この制限は、望ましい動作を妨げないことに注意してください。マルチピア実装を構築する前の直観とは異なり、1つのデバイスが招待を受け入れて別のデバイスに接続するとboth接続され、接続デリゲートメソッドを受信し、他のメッセージを送信できます。

したがって、ブラウズとアドバタイズの両方のデバイスを接続している場合は、招待状を自由に送信しますが、ペアの一方のみを受け入れます

2つの招待のうち1つだけを受け入れるという問題は、無数の方法で解決できます。まず、招待状のcontext引数として任意のオブジェクトまたは辞書(データとしてアーカイブ)を渡すことができることを理解してください。したがって、両方のデバイスは、他のデバイス(およびもちろんそれ自体)に関する任意の情報にアクセスできます。したがって、少なくとも次の戦略を使用できます。

  • 単にcompare: peerIDの表示名。しかし、これらが等しくないという保証はありません。
  • マルチピアコントローラが初期化された日付を保存し、それを比較に使用する
  • 各ピアにUUIDを与え、これを比較のために送信します(私の手法では、各デバイス-実際にデバイス上のアプリの各ユーザー-が使用する永続的なUUIDを持っています)。
  • etc-NSCodingとcompare:の両方をサポートするオブジェクトであれば問題ありません。
11
SG1

同様の問題が発生しています。あるiOSデバイスでアプリを実行して別のiOSデバイスに接続し、その後終了して再起動した場合(たとえば、Xcodeから再実行した場合)、Connectedメッセージが表示され、次にNot Connectedが表示されるようです。少し後でメッセージ。これは私を失望させました。しかし、もっと注意深く見ると、接続されていないメッセージは実際には、接続されたものとは異なるpeerIdに対するものであることがわかります。

ここでの問題は、私が見たほとんどのサンプルが、peerIDのdisplayNameを気にするだけであり、同じデバイス/ displayNameに対して複数のpeerIDを取得できるという事実を無視していることだと思います。

私は最初にdisplayNameをチェックしてから、ポインターの比較を行うことにより、peerIDが同じであることを確認しています。

- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {

    MyPlayer *player = _players[peerID.displayName];

    if ((state == MCSessionStateNotConnected) &&
        (peerID != player.peerID)) {
        NSLog(@"remnant connection drop");
        return; // note that I don't care if player is nil, since I don't want to
                // add a dictionary object for a Not Connecting peer.
    }
    if (player == nil) {
        player = [MyPlayer init];
        player.peerID = peerID;
        _players[peerID.displayName] = player;
    }
    player.state = state;

...
3
mahboudz

接続要求を受け入れた直後に切断していました。状態を観察すると、MCSessionStateConnectedからMCSessionStateNotConnectedに変化するのがわかりました。

私は次のセッションを作成しています:

[[MCSession alloc] initWithPeer:peerID]

セキュリティ証明書を扱うインスタンス化方法ではありません:

 - (instancetype)initWithPeer:(MCPeerID *)myPeerID securityIdentity:(NSArray *)identity encryptionPreference:(MCEncryptionPreference)encryptionPreference 

上記のAndrewのヒントに基づいて、デリゲートメソッドを追加しました

   - (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler {
         certificateHandler(YES);
     }

そして切断は停止しました。

1
DannyJi