私のアプリはGKSessionModePeerでGKSessionを使用しています。これは長時間実行されるアプリであり、ユーザーはバックグラウンドに戻って後で戻ることができるはずなので、任意に接続および切断するピアを処理する必要があります。これはほとんどの場合正常に機能します。ただし、ピアが切断されると、実際に切断されたデバイスだけでなく、実際に接続されている他のデバイスについても、didChangeState:GKPeerStateDisconnectedで他のデバイスに通知される場合があります。
以下のコードと4つのデバイス(すべてiOS 5)を使用して、この動作を再現できます。すべてが期待どおりに進むと、デバイスAがアプリを終了すると、他のすべてのデバイスに通知が届き、それらのデバイスのログ出力は次のようになります。
Service: didChangeState: peer A disconnected (12345)
ただし、しばらくして、デバイスが切断されると(たとえば、もう一度A)、他のデバイスは、切断されなかったデバイスに対して追加のコールバックを受け取ります。たとえば、デバイスCは次のようになります。
Service: didChangeState: peer A disconnected (...) // expected
Service: didChangeState: peer B disconnected (...) // never disconnected
同じ頃、切断しているデバイスのログにこの種のメッセージが表示されることがありますが、実際に関連しているかどうかはわかりません。
dnssd_clientstub DNSServiceRefDeallocate called with NULL DNSServiceRef
および/または
dnssd_clientstub DNSServiceProcessResult called with DNSServiceRef with no ProcessReply function
これが発生すると、GKSessionは悪い状態にあるように見え、接続と切断を正しく処理できなくなります。良好な状態に戻すには、すべてのデバイスでアプリを強制終了し、少し待ってから最初からやり直す必要があります。
バックグラウンドに移動するときにGKSessionを処理するさまざまな方法を試しました(available = NOに設定し、切断せず、何もしません)。どれもうまくいきませんでした。
他の誰かがこの振る舞いに遭遇しましたか(そしてそれを解決しましたか)?
AppDelegateでの単純な再現ケース(アークを使用):
- (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