Можно ли работать с CALayer не из главного потока?

Ответ

Нет, все операции с CALayer, влияющие на отображение, должны выполняться на главном потоке (Main Thread).

Правило: Core Animation не является thread-safe. Изменения иерархии слоев, анимации или свойств, влияющих на отображение, требуют главного потока.

Неправильно (вызовет краш или неопределенное поведение):

DispatchQueue.global().async {
    // ОПАСНО: изменение иерархии слоев не из главного потока
    let newLayer = CALayer()
    newLayer.backgroundColor = UIColor.red.cgColor
    view.layer.addSublayer(newLayer) // ✗ Может вызвать краш
}

Правильно (все UI-операции на главном потоке):

// 1. Подготовка данных в фоне
DispatchQueue.global(qos: .userInitiated).async {
    let image = self.processHeavyImage()
    let path = self.createComplexBezierPath()

    // 2. Применение изменений на главном потоке
    DispatchQueue.main.async {
        let imageLayer = CALayer()
        imageLayer.contents = image.cgImage
        imageLayer.frame = self.imageView.bounds

        let shapeLayer = CAShapeLayer()
        shapeLayer.path = path.cgPath
        shapeLayer.fillColor = UIColor.blue.cgColor

        // Безопасное добавление на главном потоке
        self.view.layer.addSublayer(imageLayer)
        self.view.layer.addSublayer(shapeLayer)
    }
}

Исключения (что можно делать в фоне):

  • Создание CGImage, CIImage, CGPath
  • Вычисления для анимаций (значения from, to, duration)
  • Подготовка данных для contents слоя

Проверка потока:

// Всегда проверяйте, если не уверены
if Thread.isMainThread {
    layer.opacity = 0.5
} else {
    DispatchQueue.main.async {
        layer.opacity = 0.5
    }
}

Диагностика: Включите CADebugger (CA_DEBUG_TRANSACTIONS=1) для обнаружения нарушений потокобезопасности.

Ответ 18+ 🔞

Да ты послушай, что за дичь творится с этими слоями! Сидишь такой, кодишь в фоне, думаешь — ну, я же умный, всё распараллелю, производительность подниму до небес. А потом — бац! — краш, и приложение твоё, как та Муму, на дно пошло. И ты сидишь, чешешь репу: «Что за хуйня? Вроде всё правильно!».

А оказывается, вся эта ваша Core Animation — она, блядь, как та самая барыня-помещица из рассказа. Требует, сука, чтобы все поклоны и приседания с CALayer делались исключительно в главной гостиной, на главном потоке! Попробуй сунуться с фонового — получишь по ебалу сразу, без предупреждения. «Я в этот дом не ногой!» — кричит система, и выкидывает тебя с ошибкой.

Вот смотри, как некоторые умудряются накосячить, прям как Герасим с мешком:

DispatchQueue.global().async {
    let newLayer = CALayer()
    newLayer.backgroundColor = UIColor.red.cgColor
    view.layer.addSublayer(newLayer) // ✗ ЭТО ПИЗДЕЦ, НЕ ДЕЛАЙ ТАК!
}

Вот это — чистой воды самоубийство. Ты в фоновом потоке лезешь в иерархию слоёв, которая принадлежит главному. Это всё равно что пытаться переставить мебель в чужой квартире, пока хозяин спит. Закончится всё плохо, поверь.

А делать надо вот так, с умом, как порядочный человек:

// 1. Готовь свои штуки где хочешь, хоть в сарае (в фоне)
DispatchQueue.global(qos: .userInitiated).async {
    let image = self.processHeavyImage() // Тяжелая картинка
    let path = self.createComplexBezierPath() // Сложный путь

    // 2. А ВЫСТАВЛЯТЬ напоказ — только в парадной зале (на главном)!
    DispatchQueue.main.async {
        let imageLayer = CALayer()
        imageLayer.contents = image.cgImage
        imageLayer.frame = self.imageView.bounds

        let shapeLayer = CAShapeLayer()
        shapeLayer.path = path.cgPath
        shapeLayer.fillColor = UIColor.blue.cgColor

        // Вот теперь можно, барин разрешил
        self.view.layer.addSublayer(imageLayer)
        self.view.layer.addSublayer(shapeLayer)
    }
}

Видишь разницу? Всю грязную работу — вычисления, создание CGImage, CGPath — можно делать в фоне, ебушки-воробушки! Это как дрова наколоть в заднем дворе. Но тащить эти дрова и класть в камин в гостиной — это уже нужно делать, когда все проснулись и готовы.

И да, если сомневаешься — проверь, не мудак ли ты, и не в фоновом ли потоке находишься:

if Thread.isMainThread {
    layer.opacity = 0.5 // Всё чики-пуки
} else {
    DispatchQueue.main.async {
        layer.opacity = 0.5 // А вот теперь — чики-пуки
    }
}

А если совсем припёрло и непонятно, где косяк — включи CADebugger (CA_DEBUG_TRANSACTIONS=1). Он тебе, как совесть Герасима, всё про твои потоки выскажет. Только не удивляйся потом, почему у тебя волосы дыбом встали.

Короче, запомни раз и навсегда: UI, слои, анимации — это священная корова главного потока. Не трогай её из фона, а то получишь хуй с горы вместо работающего приложения. Всё, вопрос закрыт.