Подтвердить что ты не робот

Почему мой коллега MCSession отключается случайно?

Я использую MCNearbyServiceBrowser и MCNearbyServiceAdvertiser для объединения двух одноранговых узлов в MCSession. Я могу отправлять данные между ними, используя метод MCSession sendData. Кажется, что все работает как ожидалось, пока я случайно (и не из-за какого-либо события, которое я контролирую) получаю MCSessionStateNotConnected через сеанс обработчика MCSessionDelegate didChangeState. Кроме того, массив MCSession connectedPeers больше не имеет моих сверстников.

Два вопроса: почему? и Как я могу отключить MCSession?

4b9b3361

Ответ 1

Это ошибка, о которой я только что сообщил Apple. Документы утверждают, что обратный вызов didReceiveCertificate не является обязательным, но это не так. Добавьте этот метод в свой MCSessionDelegate:

- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
 {
     certificateHandler(YES);
 }

Случайные разъединения должны прекратиться.

Ответ 2

ОБНОВЛЕНИЕ После того, как вы использовали билет поддержки Apple, они подтвердили, что слишком часто вызывает sendData и слишком много данных может привести к отключению.

У меня были разъединения при попадании точек останова и при фоновой настройке. Поскольку точки останова не будут выполняться в магазине приложений, вам необходимо обработать случай фоновой записи, начав фоновое задание, когда ваше приложение вот-вот войдет в фон. Затем завершите эту задачу, когда ваше приложение вернется на передний план. На iOS 7 это дает вам около 3 фоновых минут, которые лучше, чем ничего.

Дополнительной стратегией было бы запланировать локальное уведомление, возможно, за 15 секунд до истечения вашего фонового времени с помощью [[UIApplication sharedApplication] backgroundTimeRemaining], таким образом вы сможете вернуть пользователя в приложение до его приостановки, а многоуровневая структура должна быть неисправность. Возможно, местное уведомление предупредит их, что их сеанс истечет через 10 секунд или что-то еще...

Если фоновая задача истекает, и приложение все еще находится в фоновом режиме, вам необходимо снести все, что связано с многоуровневой связью, иначе вы получите сбои.

- (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
}

Вам также понадобится этот обработчик в вашем делете MCSession из-за ошибки Apple:

- (void) session:(MCSession*)session didReceiveCertificate:(NSArray*)certificate fromPeer:(MCPeerID*)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
 {
     if (certificateHandler != nil) { certificateHandler(YES); }
 }

Ответ 3

Есть много причин этого, и два ответа до сих пор являются верными в моем опыте. Другой, который вы найдете в других подобных вопросах, следующий: Только один одноранговый узел может принять другое приглашение.

Итак, чтобы уточнить, если вы настроите приложение, в котором все устройства являются как рекламодателями, так и браузерами, любые устройства могут свободно приглашать других, которые могут присоединиться к сеансу. Однако между любыми двумя заданными устройствами только одно устройство может фактически принять приглашение и подключиться к другому устройству. Если оба устройства принимают приглашения друг друга, они будут отключены в течение минуты или меньше.

Обратите внимание, что это ограничение не мешает желаемому поведению, потому что - в отличие от того, что моя интуиция заявила до того, как я построил свою реализацию многоузлового объединения - когда одно устройство принимает приглашение и подключается к другому устройству, они оба становятся подключенными и получают методы делегирования соединений и могут отправлять друг друга.

Поэтому, если вы подключаете устройства, которые просматривают и рекламируют, отправляйте приглашения свободно, но только принимайте одну из пары.

Проблема только принятия одного из двух приглашений может быть решена множеством способов. Для начала поймите, что вы можете передать любой произвольный объект или словарь (заархивированные как данные) в качестве аргумента context в приглашении. Поэтому оба устройства имеют доступ к любой произвольной информации о другой (и, конечно же, о себе). Таким образом, вы можете использовать, по крайней мере, следующие стратегии:

  • просто compare: отображаемое имя peerID. Но нет гарантии, что они не будут равными.
  • сохраните дату, когда ваш контроллер мультиплеера был инициализирован, и используйте это для сравнения
  • дайте каждому партнеру UUID и отправьте это для сравнения (моя техника, в которой каждое устройство - действительно, каждый пользователь приложения на устройстве - имеет постоянный UUID, который он использует).
  • и т.д. - любой объект, который поддерживает как NSCoding, так и compare:, будет работать нормально.

Ответ 4

У меня были подобные проблемы. Кажется, что если я запустил приложение на одном устройстве iOS и подключился к другому, то закройте и перезапустите (скажем, когда я перейду с Xcode), то я в ситуации, когда я получаю сообщение Connected, а затем не подключен сообщение немного позже. Это меня отбросило. Но, глядя более внимательно, я вижу, что сообщение Not Connected на самом деле предназначено для другого peerId, чем тот, который подключился.

Я думаю, проблема в том, что большинство образцов, которые я видел, просто заботятся о displayName для peerID и пренебрегают тем фактом, что вы можете получить несколько peerID для одного и того же устройства /displayName.

Теперь я сначала проверяю displayName, а затем проверяю, что peerID тот же, выполнив сравнение указателей.

- (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;

...

Ответ 5

Я немедленно отключился после того, как принял запрос на соединение. Наблюдая за состоянием, я увидел, что он изменился с MCSessionStateConnected на MCSessionStateNotConnected.

Я создаю свои сеансы с помощью:

[[MCSession alloc] initWithPeer:peerID]

НЕ используется метод создания экземпляров сертификатов безопасности:

 - (instancetype)initWithPeer:(MCPeerID *)myPeerID securityIdentity:(NSArray *)identity encryptionPreference:(MCEncryptionPreference)encryptionPreference 

Основываясь на советнике Andrew выше, я добавил метод делегата

   - (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler {
         certificateHandler(YES);
     }

и отсоединения остановлены.