objective c - ¿Por qué mi par MCSession se desconecta al azar?
objective-c ios7 (5)
Estoy usando MCNearbyServiceBrowser y MCNearbyServiceAdvertiser para unir dos pares a una MCSession. Puedo enviar datos entre ellos usando el método sendData de MCSession. Todo parece funcionar como se espera hasta que aleatoriamente (y no debido a ningún evento que controlo) reciba un MCSessionStateNotConnected a través del controlador MCSessionDelegate didChangeState de la sesión. Además, la matriz connectedPeers de MCSession ya no tiene mis compañeros.
Dos preguntas: ¿Por qué? y ¿Cómo evito que la MCSession se desconecte?
ACTUALIZACIÓN Después de usar un ticket de soporte para Apple, confirmaron que llamar a sendData con demasiada frecuencia y con demasiados datos puede causar desconexiones.
He tenido desconexiones al golpear los puntos de quiebre y al retroceder. Debido a que los puntos de quiebre no ocurrirán en la tienda de aplicaciones, debe manejar el caso de fondo iniciando una tarea en segundo plano cuando su aplicación esté a punto de ingresar al fondo. Luego termine esta tarea cuando su aplicación regrese al primer plano. En iOS 7 esto te da unos 3 minutos de fondo que es mejor que nada.
Una estrategia adicional sería programar una notificación local durante aproximadamente 15 segundos antes de que caduque el tiempo de fondo utilizando [[UIApplication sharedApplication] backgroundTimeRemaining]
, de esa manera puede traer al usuario de vuelta a la aplicación antes de que se suspenda y el marco de trabajo multi-par tiene que ser apagado Tal vez la notificación local les advierta que su sesión caducará en 10 segundos o algo así ...
Si la tarea en segundo plano expira y la aplicación todavía está en segundo plano, debe eliminar todo lo relacionado con la conectividad de múltiples pares; de lo contrario, se producirán bloqueos.
- (void) createExpireNotification
{
[self killExpireNotification];
if (self.connectedPeerCount != 0) // if peers connected, setup kill switch
{
NSTimeInterval gracePeriod = 20.0f;
// create notification that will get the user back into the app when the background process time is about to expire
NSTimeInterval msgTime = UIApplication.sharedApplication.backgroundTimeRemaining - gracePeriod;
UILocalNotification* n = [[UILocalNotification alloc] init];
self.expireNotification = n;
self.expireNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:msgTime];
self.expireNotification.alertBody = TR(@"Text_MultiPeerIsAboutToExpire");
self.expireNotification.soundName = UILocalNotificationDefaultSoundName;
self.expireNotification.applicationIconBadgeNumber = 1;
[UIApplication.sharedApplication scheduleLocalNotification:self.expireNotification];
}
}
- (void) killExpireNotification
{
if (self.expireNotification != nil)
{
[UIApplication.sharedApplication cancelLocalNotification:self.expireNotification];
self.expireNotification = nil;
}
}
- (void) applicationWillEnterBackground
{
self.taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
{
[self shutdownMultiPeerStuff];
[[UIApplication sharedApplication] endBackgroundTask:self.taskId];
self.taskId = UIBackgroundTaskInvalid;
}];
[self createExpireNotification];
}
- (void) applicationWillEnterForeground
{
[self killExpireNotification];
if (self.taskId != UIBackgroundTaskInvalid)
{
[[UIApplication sharedApplication] endBackgroundTask:self.taskId];
self.taskId = UIBackgroundTaskInvalid;
}
}
- (void) applicationWillTerminate
{
[self killExpireNotification];
[self stop]; // shutdown multi-peer
}
También querrá este controlador en su delegado de MCSession debido a un error de Apple:
- (void) session:(MCSession*)session didReceiveCertificate:(NSArray*)certificate fromPeer:(MCPeerID*)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
{
if (certificateHandler != nil) { certificateHandler(YES); }
}
Este es un error, que acabo de informar a Apple. Los documentos afirman que la didReceiveCertificate
llamada de didReceiveCertificate
es opcional, pero no lo es. Agregue este método a su MCSessionDelegate
:
- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
{
certificateHandler(YES);
}
Las desconexiones aleatorias deben cesar.
Hay muchas causas de esto, y las dos respuestas hasta ahora son correctas en mi experiencia. Otra que encontrará en otras preguntas similares es esta: solo un compañero puede aceptar la invitación de otra persona .
Por lo tanto, para aclarar, si configura una aplicación donde todos los dispositivos son tanto anunciantes como navegadores, cualquier dispositivo puede invitar libremente a los demás que se encuentren a unirse a una sesión. Sin embargo, entre dos dispositivos determinados, solo un dispositivo puede aceptar la invitación y conectarse al otro dispositivo. Si ambos dispositivos aceptan las invitaciones de los demás, se desconectarán en un minuto o menos.
Tenga en cuenta que esta limitación no evita el comportamiento deseado porque, a diferencia de lo que declaró mi intuición antes de crear mi implementación de múltiples dispositivos, cuando un dispositivo acepta una invitación y se conecta a otro, ambos se conectan y reciben métodos de delegado de conexión y pueden enviarse mensajes entre sí. .
Por lo tanto, si está conectando dispositivos que navegan y anuncian, envíe invitaciones libremente pero solo acepte uno de un par .
El problema de aceptar solo una de dos invitaciones se puede resolver de muchas maneras. Para comenzar, comprenda que puede pasar cualquier objeto arbitrario o diccionario (archivado como datos) como argumento de context
en una invitación. Por lo tanto, ambos dispositivos tienen acceso a cualquier información arbitraria sobre el otro (y por supuesto). Entonces, podrías usar al menos estas estrategias:
- simplemente
compare:
el nombre para mostrar del peerID. Pero no hay garantía de que estos no sean iguales. - almacenar la fecha en que se inicializó su controlador de múltiples dispositivos y usarlo para comparar
- Proporcione a cada par un UUID y envíelo para comparar (mi técnica, en la que cada dispositivo (de hecho, cada usuario de la aplicación en un dispositivo) tiene un UUID persistente que emplea).
- etc., cualquier objeto que admita tanto NSCoding como
compare:
bien.
Me desconecté inmediatamente después de aceptar la solicitud de conexión. Observando el estado, lo vi cambiar de MCSessionStateConnected a MCSessionStateNotConnected.
Estoy creando mis sesiones con:
[[MCSession alloc] initWithPeer:peerID]
NO es el método de instanciación que trata con los certificados de seguridad:
- (instancetype)initWithPeer:(MCPeerID *)myPeerID securityIdentity:(NSArray *)identity encryptionPreference:(MCEncryptionPreference)encryptionPreference
Basado en el consejo anterior de Andrew, agregué el método delegado
- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler {
certificateHandler(YES);
}
y las desconexiones se detuvieron.
He estado teniendo problemas similares. Sin embargo, parece que si he ejecutado mi aplicación en un dispositivo iOS y conectado a otra, luego salgo y reinicio (por ejemplo, cuando vuelvo a ejecutar Xcode), entonces estoy en una situación en la que aparece un mensaje Conectado y luego no conectado. mensaje un poco más tarde. Esto me estaba tirando. Pero mirando más detenidamente, puedo ver que el mensaje No Conectado está diseñado para un interlocutor diferente del que se ha conectado.
Creo que el problema aquí es que la mayoría de las muestras que he visto solo se preocupan por las displayName del peerID, y descuidan el hecho de que puede obtener múltiples peerID para el mismo dispositivo / displayName.
Ahora estoy comprobando primero el displayName y luego verificando que el peerID es el mismo, haciendo una comparación de los punteros.
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
MyPlayer *player = _players[peerID.displayName];
if ((state == MCSessionStateNotConnected) &&
(peerID != player.peerID)) {
NSLog(@"remnant connection drop");
return; // note that I don''t care if player is nil, since I don''t want to
// add a dictionary object for a Not Connecting peer.
}
if (player == nil) {
player = [MyPlayer init];
player.peerID = peerID;
_players[peerID.displayName] = player;
}
player.state = state;
...