My app uses GKSession with GKSessionModePeer. It has to handle peers arbitrarily connecting and disconnecting because this is a long running app and users should be able to go to background and come back later. This works fine most of the time. But sometimes, when a peer disconnects, other devices get notified with didChangeState:GKPeerStateDisconnected not only for the device that really disconnected but also for other devices that are actually still connected.
I can reproduce this behavior with the code below and 4 devices (all on iOS 5). When everything goes as expected, when device A quits the app, all other devices get notified and the log output on those devices is:
Service: didChangeState: peer A disconnected (12345)
But after a while, when a device disconnects (say A again), other devices will get additional callbacks for devices that didn't disconnect. For example, device C would get:
Service: didChangeState: peer A disconnected (...) // expected
Service: didChangeState: peer B disconnected (...) // never disconnected
Around the same time I am sometimes seeing these kind of messages in the logs of the disconnecting device, not clear if they are actually related:
dnssd_clientstub DNSServiceRefDeallocate called with NULL DNSServiceRef
and/or
dnssd_clientstub DNSServiceProcessResult called with DNSServiceRef with no ProcessReply function
Once this happens, GKSession seems to be in a bad state and no longer correctly handles connects and disconnects. To get back into a good state I have to hard kill the app on all devices, wait a little, and start over.
I have tried different ways of handling GKSession when going to background (only setting available=NO and not disconnecting, not doing anything at all), none of which worked any better.
Has anybody else run into this behavior (and solved it)?
Simple repro case in an AppDelegate (using arc):
- (void)startGKSession
{
self.gkSession = [[GKSession alloc] initWithSessionID:nil displayName:nil sessionMode:GKSessionModePeer];
gkSession.disconnectTimeout = 10;
gkSession.delegate = self;
gkSession.available = YES;
}
- (void)shutdownGKSession
{
gkSession.available = NO;
[gkSession disconnectFromAllPeers];
gkSession.delegate = nil;
gkSession = nil;
[self.connectedDevices removeAllObjects];
}
- (void)connectToPeer:(NSString *)peerId
{
[gkSession connectToPeer:peerId withTimeout:10];
}
- (void)session:(GKSession *)session peer:(NSString *)peerId didChangeState:(GKPeerConnectionState)state
{
switch (state) {
case GKPeerStateAvailable:
NSLog(@"Service: didChangeState: peer %@ available, connecting (%@)", [session displayNameForPeer:peerId], peerId);
[self performSelector:@selector(connectToPeer:) withObject:peerId afterDelay:.5];
break;
case GKPeerStateUnavailable:
NSLog(@"Service: didChangeState: peer %@ unavailable (%@)", [session displayNameForPeer:peerId], peerId);
break;
case GKPeerStateConnected:
NSLog(@"Service: didChangeState: peer %@ connected (%@)", [session displayNameForPeer:peerId], peerId);
break;
case GKPeerStateDisconnected:
NSLog(@"Service: didChangeState: peer %@ disconnected (%@)", [session displayNameForPeer:peerId], peerId);
break;
case GKPeerStateConnecting:
NSLog(@"Service: didChangeState: peer %@ connecting (%@)", [session displayNameForPeer:peerId], peerId);
break;
}
}
- (void)session:(GKSession *)session didReceiveConnectionRequestFromPeer:(NSString *)peerID
{
[session acceptConnectionFromPeer:peerID error:nil];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.connectedDevices = [[NSMutableArray alloc] init];
[self startGKSession];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self shutdownGKSession];
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[self startGKSession];
}
@end