Почему анимация в UIKit продолжает работать при блокировке главного потока?

Ответ

Анимации в UIKit выполняются не на главном потоке, а в отдельном потоке рендеринга (render server), управляемом фреймворком Core Animation.

Причины:

  1. Архитектура Core Animation: Изменения анимируемых свойств (например, frame, alpha) фиксируются в модели слоев (model layer). Затем система рендеринга (render server) в отдельном процессе берет эти изменения и интерполирует кадры независимо от состояния главного потока приложения.
  2. CADisplayLink: Этот таймер, синхронизированный с частотой обновления экрана, также планируется в общий run loop (RunLoop.common), а не только в главный.

Пример:

// Анимация запускается
UIView.animate(withDuration: 5.0) {
    myView.center.y += 200
}
// Блокировка главного потока НЕ остановит анимацию
Thread.sleep(forTimeInterval: 3.0) // Анимация продолжит выполняться

Важное ограничение: Хотя анимация будет отрисовываться, пользовательские взаимодействия (тапы, жесты) и обновление неанимируемого UI обрабатываются на главном потоке, поэтому они будут недоступны на время блокировки.

Ответ 18+ 🔞

Вот же ж, классическая история, как нас всех наебали с потоками! Сидишь, блядь, думаешь: "Ага, анимация — это ж UI, значит, главный поток, заебись". А потом раз — и выясняется, что всё не так, сука, однозначно.

Смотри, в чём прикол. Вся эта движуха с плавными перемещениями и прозрачностями — это не наш, убогий, главный поток тянет. Это, блядь, целый отдельный цирк с конями под названием Core Animation. У них там свой, ёпта, render server — отдельный процесс, который и занимается всей этой хуйнёй: берёт твои желания ("хочу, чтобы вьюха уехала на 200 пикселей за 5 секунд") и сам, как взрослый дядя, рисует все промежуточные кадры.

Почему так, нахуй?

  1. Архитектура, блядь. Когда ты меняешь анимируемое свойство (типа frame или alpha), ты по сути только записываешь финальное состояние в какую-то внутреннюю бумажку (model layer). А потом приходит этот самый render server, смотрит на бумажку и говорит: "О, я понял, что хочет этот долбоёб. Ща сделаю красиво". И делает — независимо от того, чем в это время занят твой главный поток: считает ли он до миллиарда или просто спит, сука.
  2. CADisplayLink — этот таймер, который тикает в такт с экраном, — тоже, оказывается, не всегда привязан к главному потоку как собака на цепи. Он может болтаться в общем RunLoop.common, так что даже если твой главный поток встал колом, таймер может продолжать стучать.

Вот, смотри, пиздатый пример:

// Запускаем анимацию — вьюха поползёт вниз
UIView.animate(withDuration: 5.0) {
    myView.center.y += 200
}
// А теперь — внимание! — ебём главный поток нахуй на 3 секунды
Thread.sleep(forTimeInterval: 3.0) // Анимация, сука, ПРОДОЛЖИТСЯ!

Вот это да, ёпта! Вьюха будет ползти как ни в чём не бывало, даже пока главный поток в отключке. НО! И вот тут самое важное, блядь, ограничение.

Хотя картинка и будет двигаться, весь остальной мир для пользователя замрёт. Тапы не будут работать, жесты — тоже, а любой неанимируемый интерфейс просто повиснет, как будто его вырубило. Потому что вся эта логика — обработка событий, обновление текстов, пересчёт лэйаута — всё это, увы, по-прежнему сидит на главном потоке. Так что анимация-то будет, но приложение будет выглядеть как полный мудак, который не реагирует.