Мое приложение использует GKSession с GKSessionModePeer. Он должен обрабатывать одноранговые соединения, произвольно подключаясь и отключаясь, потому что это приложение работает долго, и пользователи должны иметь возможность перейти в фоновый режим и вернуться позже. Это прекрасно работает большую часть времени. Но иногда, когда одноранговый разъединяется, другие устройства получают уведомление с 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 при переходе в фоновый режим (доступны только настройки = НЕТ и отсутствие отключения, вообще ничего не делал), ни один из которых не работал лучше.
Кто-нибудь еще сталкивался с этим поведением (и решил его)?
Простой случай воспроизведения в 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