Artist’s rendition of one of the YMC Mobile team members
It allows nearby iOS devices to communicate with each other in a peer to peer fashion. Depending on what connectivity options are available, it uses infrastructure Wi-Fi networks (iPhone 4 and up), peer to peer Wi-Fi (iPhone 5/iPad 4th Gen and up), and Bluetooth (iPhone 4S/iPad 2 and up). This means if peer to peer Wi-Fi or Bluetooth is available, it works even without a common Wi-Fi network between the peers. And it requires no Internet connection at all which is useful in cases where a connection to the Internet is not available or very expensive (e.g. when the user is abroad).
There is an overview of how to use Multipeer Connectivity on NSHipster, if you’re not familiar with using the framework.
This sounds very useful – if it only would work properly. As with Game Kit and Game Center, you can tell that Multipeer Connectivity Framework is not something Apple is using themselves.
In the process of creating a custom version of RADIOUS for a customer to support more than two clients and to handle reconnection if one client drops out, we hit several major bugs in the Framework. We’d like to share the biggest roadblocks and how to get around them (if possible). If you’re also running into those problems, please do also file a bug report with Apple. The issue numbers are noted in this post and its contents are on Open Radar.
Noticing that someone is gone
This sounds like an easy task, but unfortunately, the framework doesn’t always notify you when a peer is gone. To implement reconnection however, we do need to know.
The problem is two fold:
- When browsing for nearby peers, we need to know when a peer is out of range so we can remove it from the UI as a candidate to connect to. Also, when the peer was previously connected, we need to see a disappearance and reappearance to know that we can initiate the connection again.
- When connected, we need to know when a peer drops out
Problem 1: Detecting when a peer is not nearby
Before a connection can be established, we need to know what peers are nearby. Each peer that can accept connections runs an advertiser and peers looking to connect to someone run a service browser.
The browser can either feed a UI where the user can initiate connections or the app can handle those events programmatically.
Usually, when a peer is out of range or has stopped its advertiser,
-[MCNearbyServiceBrowserDelegate browser:lostPeer:] is called so you can react to it accordingly. This works in most cases but unfortunately it’s only “in most cases”. If the iOS simulator is browsing, the peer always sticks around when it drops out of the network. Even completely re-instantiating the peer browser does not help.
Not noticing that a peer got out of range and then back in again is not a big problem for the peer browser UI since the connection will actually succeed when the user tells the app to connect.
But for automated actions, it is a problem since you can’t be sure when the other peer is back in range.
(Filed as rdar://17574895).
Workaround: None so far.
Problem 2: Detecting when a peer lost connection
When the connection to a peer is established or lost, the session calls
-[MCSessionDelegate session:peer:didChangeState:] on its delegate. Except when it doesn’t. Sometimes, we get the notification much later, in the order of half a minute. Sometimes, we don’t get any notification when a peer drops out at all.
(Filed as rdar://17640200)
Workaround: We implemented a heartbeat which regularly sends a message to each connected peer and expects an answer. After a certain amount of consecutive heartbeat failures, we mark the peer as disconnected. Works great with two peers, as you can just disconnect from the session when the heartbeat fails. Unfortunately, for sessions with more than two peers, this is only moderately useful, as a peer can’t be manually removed from the session (filed as rdar://17640200). To work around this, we create a session for each pair of peers. More on that a bit further down.
Reconnecting after connection Loss
“That should be easy”, we thought. “Just send out an invitation automatically when the dropped out peer appears again. The other peer would just automatically accept the invitation when it’s reconnecting.”
As discussed above, the “when the dropped out peer appears again” is not detected reliably. But in the cases we can detect the condition and react accordingly, we still hit some brick walls.
Problem 3: Reconnecting in a session with more than two peers
One of the biggest problems we hit was that a peer re-joining a session doesn’t re-establish connection to all peers. To illustrate the problem, we have a session with three peers connected to each other:
A session with three peers, “A”, “B” and “C” connected
Now let’s say peer “C” goes out of range:
Peer “C” is now disconnected
Now peer “C” get back into range and peer “A” connects to to peer “C”. We expect that all peers would get connected to each other again, but the actual result is something like this:
Peers “B” and “C” can’t talk to each other
Peer “A” can talk to peers “B” and “C”, but peers “B” and “C” is only connected to peer “A”.
The funny thing is though, peers “B” and “C” each get
-[MCSessionDelegate session:peer:didChangeState:] called for the not connected peer with
MCSessionStateConnected, i.e. the framework tells peer “B” that peer “C” is now connected (and vice versa). But in reality, all attempts of peers “B” and “C” talking to each other fails with a “Peer is not connected” error message.
(Filed as rdar://17533347)
Workaround: Create a session for each connected peer. In the above example, peer “A” would have a session with only peer “B” and another session with only peer “C” in it. The downside of this solution is that it prevents one of Multipeer Connectivity’s flagship features: If a peer is not directly reachable, the data can not be relayed by another peer anymore.
Problem 4: Being invited after a connection loss
-[MCNearbyServiceAdvertiserDelegate advertiser:didReceiveInvitationFromPeer:(…)] doesn’t get called on the advertiser’s delegate. The retry mechanism on the inviter’s side didn’t help as all subsequent invitations also didn’t come through.
(Filed as rdar://17637621)
Workaround: Re-instantiate the browser and advertiser. Just stopping and starting them again is not sufficient, only creating new instances helped in our case. We do this each time a peer changes to the disconnected state.
Multipeer Connectivity has many great uses, but unfortunately it’s still in its infancy and not up to the quality levels of Apple’s more mature and often used frameworks. It was a ridiculous amount of extra work we needed to put into a seemingly simple feature such as automatic reconnection. Unfortunately, we’re not the only ones with problems.
We are considering cleaning up and extracting our session management code into a library and releasing it. Before we do that, we need to ship first though.