Multipeer Connectivity Framework - Lost Peer stays in Session

Arjun Mehta picture Arjun Mehta · Apr 11, 2014 · Viewed 9.2k times · Source

I wonder if this Multipeer Connectivity framework is ready for use in the real world, given all the bugs that have been encountered by the community. I think I'm setting it up right, but all the other sample projects I've tried encounter similar issues.

The problem I'm having may be tied to some issue inherent to Bonjour or something, I can't figure it out, but basically the problem is as follows:

  • I have an active MCSession with a number of peers.
  • Now, if a device is in a session, and then force quits out, that "Peer" stays connected for an indefinite amount of time.
  • There's nothing I can do to force that user out, even though the browser:lostPeer: method is called for that peer and is no longer even showing up in the browser as "Nearby".
  • The session:peer:didChangeState: method is not called for that peer.
  • When that peer that force quitted comes back to the app, they are "Found" again by the browser:foundPeer:withDiscoveryInfo: but still also exist in the session.connectedPeers NSArray. Obviously they don't receive any data or updates about the session still and are not actually connected.
  • The only thing that seems to work to register that original peer as MCSessionStateNotConnected to the session is by reconnecting that peer to the original session. Then there is a duplicate call to session:peer:didChangeState: where the new instance of the peerID is MCSessionStateConnected and shortly after the old instance of the peerID calls with MCSessionStateNotConnected.

The sample chat application demonstrates this issue well: https://developer.apple.com/library/ios/samplecode/MultipeerGroupChat/Introduction/Intro.html

Since there doesn't seem to be any way to manually force remove a peer from the session, what should I do? Should I try and rebuild the session somehow?

This Framework seems like a bit of a mess, but I'm trying to reserve judgement!

Answer

ChrisH picture ChrisH · Apr 11, 2014

My only workaround to this type of issue has been to have a 1-1 relationship between sessions and peers. It complicates the sending of broadcasts, but at least allows for peer-level disconnects and cleanup through disconnecting/removing the session itself.

Update

To elaborate on my original answer, in order to be able to send data to connected peers it's necessary to maintain a reference to the session that was created for each peer. I've been using a mutable dictionary for this.

Once the invitation has been sent/accepted with a new session, use the MCSession delegate method to update the dictionary:

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

    if (state==MCSessionStateConnected){

        _myPeerSessions[peerID.displayName] = session;

    }
    else if (state==MCSessionStateNotConnected){

        //This is where the session can be disconnected without
        //affecting other peers
        [session disconnect];            

        [_myPeerSessions removeObjectForKey:peerID.displayName];
    }
}

All peers can be accessed with a method that returns all values of the dictionary, and in turn all connectedPeers (in this case one) for each MCSession:

- (NSArray *)allConnectedPeers {

   return [[_myPeerSessions allValues] valueForKey:@"connectedPeers"];

}

Sending data to a particular peer or via broadcast can be done with a method like this:

- (void)sendData:(NSData *)data toPeerIDs:(NSArray *)remotePeers reliable:(BOOL)reliable error:(NSError *__autoreleasing *)error {

    MCSessionSendDataMode mode = (reliable) ? MCSessionSendDataReliable : MCSessionSendDataUnreliable;

    for (MCPeerID *peer in remotePeers){

       NSError __autoreleasing *currentError = nil;

       MCSession *session = _myPeerSessions[peer.displayName];
       [session sendData:data toPeers:session.connectedPeers withMode:mode error:currentError];

       if (currentError && !error)
        *error = *currentError;
    }
}