> Как вызвать синхронную задачу на главном потоке, чтобы избежать дедлока (iOS, Swift)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: Битрикс24

Стек: iOS, Swift

> Пример ответа

Короткий ответ

Чтобы вызвать синхронную задачу на главном потоке без дедлока, используй DispatchQueue.main.sync только если текущий поток не главный. Для проверки - Thread.isMainThread. Если уже на главном, выполняй код напрямую. Альтернатива - DispatchQueue.main.async с семафором, но это сложнее и рискованнее. Главное правило: никогда не вызывай sync на главном потоке из самого главного потока - это гарантированный deadlock.

Подробное объяснение

DispatchQueue.main.sync блокирует текущий поток до завершения блока на главном. Если текущий поток - главный, блок никогда не выполнится, потому что главный поток занят ожиданием самого себя. Это классический deadlock.

Правильный паттерн:

SWIFT
func performOnMainSync(_ block: () -> Void) {
if Thread.isMainThread {
block()
} else {
DispatchQueue.main.sync(execute: block)
}
}

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

Важно: sync на главном из фонового потока допустим, но блокирует фоновый поток до завершения. Это может привести к проблемам производительности, если вызывать часто. Для разовых операций - ок.

Альтернативный подход с async и семафором:

SWIFT
let semaphore = DispatchSemaphore(value: 0)
DispatchQueue.main.async {
// работа
semaphore.signal()
}
semaphore.wait()

Этот вариант хуже: семафор блокирует текущий поток, но если текущий поток - главный, снова deadlock. Плюс сложнее отладка. Используй только если нужен async по какой-то причине.

На практике

В iOS-разработке чаще нужен async на главный поток для обновления UI. sync используется редко - например, когда нужно гарантированно дождаться результата перед продолжением, и ты точно знаешь, что не на главном потоке.

Типичные сценарии:

  • обновление модели данных перед чтением из фонового потока
  • синхронизация с UI-элементом перед его изменением
  • тесты, где нужно дождаться выполнения на главном

Лучше избегать sync вообще, если возможно. Используй async с замыканием, либо async + DispatchGroup для ожидания нескольких задач.

Пример кода

SWIFT
import Foundation
func updateUIOnMainSync(_ update: () -> Void) {
if Thread.isMainThread {
update()
} else {
DispatchQueue.main.sync(execute: update)
}
}
// Использование
DispatchQueue.global().async {
// тяжёлая работа
let result = computeSomething()
// безопасный вызов на главном
updateUIOnMainSync {
self.label.text = result
self.view.layoutIfNeeded()
}
}
// Опасный вариант - deadlock
DispatchQueue.main.async {
// Этот код выполнится на главном потоке
DispatchQueue.main.sync { // deadlock!
print("никогда не выполнится")
}
}

Как отвечать на собеседовании

Начни с главного правила: sync на главном потоке из главного - deadlock. Покажи понимание Thread.isMainThread и почему это работает. Упомяни, что async предпочтительнее для UI-обновлений. Если спросят про семафор - объясни, когда он оправдан, и какие риски.

Хорошо добавить: "Я обычно оборачиваю это в хелпер, чтобы не дублировать проверку". Это показывает практический опыт.

Что проверяет интервьюер

  • понимание модели потоков GCD
  • знание, что такое deadlock и как его избежать
  • умение писать безопасный код для многопоточности
  • осознание trade-off между sync и async

Типичные ошибки

  • вызов DispatchQueue.main.sync без проверки текущего потока
  • использование семафора на главном потоке - тот же deadlock
  • предположение, что sync всегда безопасен, если вызван из фонового потока - это правда, но блокирует поток
  • забывают, что sync на главном из фонового - это блокирующая операция, которая может вызвать задержки UI

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью