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

Невозможно добавить себя как subview

Image of Crashlytics back trace from users in the field

Мы используем Crashlytics, более 30 пользователей видели этот сбой. этот журнал сбоев от пользователей в поле. мы никогда не смогли воспроизвести его. Это работает на iOS7. Не знаю, что вызывает это, поскольку вы можете видеть, что в стеке вызовов ничего нет в нашем приложении. Кто-нибудь еще видит это или решает проблему? благодарю! Пожалуйста, не просите меня опубликовать код (см. Комментарии выше).

4b9b3361

Ответ 1

tl; dr - см. Заключение внизу.

Я тоже периодически получаю отчеты о сбоях от пользователей по этому поводу, а затем сегодня понесли негодование за себя. Это было вызвано при выборе строки в моем представлении таблицы, пытавшейся нажать новый контроллер навигации, а затем я нажал кнопку "Назад". Странно, что навигационный контроллер, который я нажал, не содержал данных. После нажатия назад вид под навигационной панелью стал черным, и приложение разбилось.

Оглядываясь, все, что я мог найти, это другие пользователи (здесь, здесь и здесь), предполагая, что это было связано с вызовом segue или двойным нажатием контроллера просмотра в быстрой последовательности. Однако я вызываю pushViewController: изнутри tableView:didSelectRowAtIndexPath: только один раз. Если вы не используете двойное нажатие на ячейку, я не понимаю, почему это может случиться со мной. Тестирование путем двойного нажатия на ячейку просмотра таблицы не смогло воспроизвести наблюдаемый сбой.

Однако, что, если пользователь дважды нажал, потому что основной поток был заблокирован в то время? (Я знаю, я знаю, никогда не блокирую основной поток!)

Чтобы проверить это, я создал несколько (довольно ужасающих, извиняющихся, быстро вырезанных и вставляемых из другого кода) методов класса (в вымышленном классе MyDebugUtilities), чтобы многократно блокировать и разблокировать основной поток, запуская его непосредственно перед тем, как я открыл контроллер представления, содержащий представление таблицы, вызвавшее сбои. Здесь код для тех, кто хочет быстро проверить это для себя (вызов [MyDebugUtilities repeatedlyBlockTheMainThread]; будет блокировать основной поток в течение 3 секунд с помощью семафора, а затем поставить в очередь другой вызов повторноBlockTheMainThread через 3 секунды, до бесконечности. Не хотите запускать этот метод повторно или он будет просто блокировать все время):

+ (void)lowCostSemaphoreWait:(NSTimeInterval)seconds
{
    // Use a semaphore to set up a low cost (non-polling) delay on whatever thread we are currently running
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC);

    dispatch_after(delayTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_signal(semaphore);
    });

    NSLog(@"DELAYING...");
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"END of delay\n\n");
}


+ (void)repeatedlyBlockTheMainThread
{    
    dispatch_async(dispatch_get_main_queue(), ^{
        // Block the main thread temporarily using a semaphore
        [MyDebugUtilities lowCostSemaphoreWait:3.0];

        // Queue up another blocking attempt to happen shortly
        dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC);
        dispatch_after(delayTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [MyDebugUtilities repeatedlyBlockTheMainThread];
        });
    });
}

Ввод вызова NSLog в мой tableView:didSelectRowAtIndexPath:, а затем попытка двойного нажатия в один из периодов времени, когда основной поток был заблокирован, действительно воспроизвело ошибку, как указано выше, - нажатие назад разбило приложение и из NSLog было очевидно, что tableView:didSelectRowAtIndexPath: вызывается дважды. Я думаю, что здесь происходит то, что прикосновения попадают в очередь, когда основной поток блокируется, а затем поставляется вместе, как только он освобождается. Двойное нажатие, когда основной поток не заблокирован, не генерирует два вызова tableView:didSelectRowAtIndexPath:, по-видимому, потому, что он уже обработал первое нажатие и обработал нажатие.

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

Чтобы решить эту проблему, действительно простым решением является создание свойства BOOL (selectionAlreadyChosen say), которое вы устанавливаете в NO в viewWillAppear: (поэтому, когда вы возвращаетесь на этот экран, он получает reset) и затем выполните tableView:willSelectRowAtIndexPath:, чтобы сделать что-то вроде:

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (self.selectionAlreadyChosen) {
        NSLog(@"BLOCKING duplicate calls to tableView:didSelectRowAtIndexPath:");
        return nil;
    }

    self.selectionAlreadyChosen = YES;
    return indexPath;
}

Не забывайте удалять любые NSLogs и вызов повторноBlockTheMainThead, когда вы закончите.

Этот точный сценарий может и не быть проблемой, которую испытывают все остальные, но я думаю, что существует проблема в том, как Apple обрабатывает одновременные множественные варианты просмотра таблиц - это не просто двойные нажатия - тестирование вышеупомянутого решения, дико нажав на одна и та же ячейка, когда основной поток был заблокирован, генерировал поток сообщений BLOCKING duplicate calls to tableView:didSelectRowAtIndexPath:.

Собственно, тестирование этого на iOS 6 - множественные вызовы на tableView:didSelectRowAtIndexPath: не были предотвращены, но они обрабатывались по-разному (это, конечно, результат вызова pushViewController:, конечно!) - на нескольких кранах iOS 6, в то время как основной поток был заблокирован, даст вам сообщение:

Предупреждение: попытка отклонить из контроллера просмотра  в то время как презентация или увольнение Выполняется!

перед тем, как бесцеремонно выгрузиться из этого UINavigationController. Так что, может быть, это не так, что это ошибка iOS 7, но обработка была изменена под обложками, и проблема, которая всегда была там, теперь проявляется как сбой.

Update:

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

Интересно, что тот, который не обнаруживает проблему, также вызывает performSegueWithIdentifier: вместо pushViewController:, когда он находится в режиме редактирования, и когда это так, он многократно вызывает tableView:didSelectRowAtIndexPath: (и, следовательно, performSegueWithIdentifier:), но что-то о вызове pushViewController:, кажется, отменяет повторные вызовы на tableView:didSelectRowAtIndexPath:.

Confused? Я знаю, что знаю. Попытка сломать "рабочий" контроллер просмотра, сделав его недоступным для редактирования, ничего не делает. Замена режима без редактирования pushViewController: на один из performSegueWithIdentifier: вызвала многократное вызов, поэтому он не имеет никакого отношения к тому, чтобы быть в режиме редактирования в tableView.

Замена контроллера рабочего вида в иерархии тем, который этого не делает (так, чтобы он был связан отношениями rootViewController, а не модальными сегментами) ФИКСИРОВАННЫЙ ПРЕДЫДУЩИЙ КОНТРОЛЛЕР ПРОСМОТРЕННОГО ПРОСМОТРА (с предыдущим исправлением выведенный из него).

Заключение - есть что-то о том, как подключается ваша иерархия просмотров и, возможно, до использования раскадровки или модальных сегментов, или, возможно, у меня есть разбитая иерархия представлений, которая вызывает это, - что вызывает несколько кратки на ячейке просмотра таблицы, которые должны быть отправлены все сразу, если ваш основной поток блокируется в момент двойного нажатия. Если вы вызываете pushViewController: в свой tableView:didSelectRowAtIndexPath:, он будет вызван несколько раз. На iOS 6 это выведет вас из контроллера нарушителя. На iOS 7 это приведет к сбою.

Ответ 2

У меня был тот же самый сбой, вызванный вызовом pushViewController/popViewController на iOS7, в то время как в методе viewDidLoad или viewWillAppear.

Кажется, что это связано с вызовом вашего # 5 выше с YES для animateTransition, когда выполняется существующий вызов той же самой функции _block_invoke. Работая для меня, ваше перемещение может измениться.

Мой стек выглядел очень похоже, но не совсем то же самое. Не уверен, что это связано с различиями в нашем коде или чем-то. Я решил свою проблему, рефакторинг последовательности загрузки, и когда я не мог, контролируя значение для "анимированного": это было только ДА на одном параллельном событии.

Ответ 3

У нас была такая же проблема в нашем проекте.

В нашем сценарии был: ViewController A имел 2 кнопки. Кнопка 1 нажимала ViewController B и кнопку 2, нажимала ViewController C на навигационный контроллер.

Если пользователь нажимал кнопки 1 и 2 в то же самое время, и после этого пытался вернуться в ViewController A (через popViewController), приложение разбилось с журналами, которые вы опубликовали. Как ни странно, это происходит только на iOS 7.

Наше временное решение заключалось в том, чтобы переопределить pushViewController навигационного контроллера, чтобы иметь настраиваемое поведение, чтобы избежать подобных сценариев обезьян. Надеюсь это поможет.

Ответ 4

Если вы нажмете (или поп) контроллер представления с анимацией (анимированный: YES), он не будет завершен сразу, а плохие вещи произойдут, если вы сделаете еще один push или pop перед завершением анимации.

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

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    UIViewController *vc = [[UIViewController alloc] init];
    [self.navigationController pushViewController:vc animated:YES];
}

Вы получите эту ошибку:

2014-07-03 11: 54: 25.051 Демо [2840: 60b] вложенная анимация push привести к повреждению навигационной панели 2014-07-03 11: 54: 25.406 Демо [2840: 60b] Завершение перехода навигации в неожиданный государство. Дерево видимости панели навигации может быть повреждено.

Там идеальное решение: https://github.com/macjam/SafeTransition

Просто добавьте файлы кода в свой проект и сделайте ваш контроллер навигации в качестве подкласса APBaseNavigationController, и вам будет хорошо делать.

Ответ 5

Похоже, что вложенные/перекрывающиеся нажатия вызывают это. Я сделал простой контейнер-оболочку для UINavigationController, который предотвращает вложенные попсы и нажатия (https://github.com/Itheme/SmartPopNavigationController - должен использоваться для того, чтобы каждый контроллер навигации в приложении работал правильно ) Невозможно воспроизвести этот сбой после того, как я использовал его на всех навигационных контроллерах. Не 100% уверены, что это не совпадение.