Изучение данного блока предполагает предварительное знание синтаксиса языка Swift. Для успешного освоения этого материала, необходимо иметь базовое понимание синтаксиса языка Swift. Это включает в себя знание основных структур данных, операторов, циклов, функций, абстракций и других ключевых элементов языка. Без этих фундаментальных знаний будет сложно понять более сложные концепции и примеры, которые будут рассматриваться в данном блоке.


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

Создание задачи

Для тех, кто знаком с GCD: Не стоит воспринимать создание задачи как аналог DispatchQueue.global().async - это совсем не так. Это не просто перенос работы на бекграунд поток - здесь наследуется контекст и не всегда происходит переход на бекграунд поток, поэтому иногда тяжёлые задачи могут попасть на главный поток и привести к фризам.

В Structured Concurrency eсть 2 способа создать задачу:

1. Создание родительской(Detached) таски.

/// Создаётся задача верхнего уровня, которая выкидывается из текущей иерархии задач,
/// Ответственность за задачу (отмену, локальное хранилище) будет лежать на разработчике
/// - Parameters:
///   - priority: приоритет задачи, по умолчанию - medium
///   - operation: код, который будет выполняться в таске
Task.detached(priority: TaskPriority?, operation: () async -> _)

2. Создание дочерней задачи. Task.

/// Создаётся задача, которая укладывается в текущую иерархию задач.
/// Так как задача будет находится в иерархии задач, значит она будет наследовать
/// исполнителя, его контекст, локальное хранилище.
/// - Parameters:
///   - priority: приоритет задачи, по умолчанию - medium
///   - operation: код, который будет выполняться в таске
Task(priority: TaskPriority?, operation: () async -> _)

animation.gif

Указание исполнителей

Класс/структура/протоколы

  1. Главный исполнитель

    Иногда у нас существуют классы, структуры, протоколы - все методы которых должны выполняться на главном потоке, и помечать каждый метод с помощью @MainActor не хочется, если методов много - то это долго. Поэтому мы можем взять и перед сущностью написать @MainActor - что будет означать, что ВСЕ методы класса будут иметь контекст главного исполнителя.

    import UIKit
    
    @MainActor
    final class MyClass { ... }
    
    @MainActor
    struct ScreenState { ... }
    
    @MainActor
    protocol ScreenDisplayLogic { ... } /// методы, содержащиеся в данном протоколе - работают с отрисовкой, поэтому им нужен главный поток.
    
  2. Глобальный исполнитель

    Если же мы просто создаём класс - то он по умолчанию имеет @globalActor. Но писать его не имеет никакого смысла, поэтому он скрыт от глаз разработчика Поэтому ещё одним из вариантов решения задачи из собеседования будет создание какого-то сервиса. Опишем логику ниже:

    /// Сервис, который будет выполнять сложные задачи
    final class HardWorkService {
        
        func doHardWork() {
            Task {
                var array = [Int]()
                (0...100_000_000).forEach {
                    array.append($0)
                }
                print(array.count)
            }
        }
    }
    
    final class TaskExampleViewController: UIViewController {
    
        let service = HardWorkService()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .green
            print("TaskExampleViewController", #function)
            doHardWork()
        }
    
        override func viewDidDisappear(_ animated: Bool) {
            super.viewDidDisappear(animated)
            print("TaskExampleViewController", #function)
        }
    
        deinit {
            print("TaskExampleViewController", #function)
        }
        
        private func doHardWork() {
    		    /// Здесь главный контекст, но когда мы обращаемся к сервису 
    		    /// и переходим в его для исполнения кода, то код метода уже будет
    		    /// иметь исполнителя класса
            service.doHardWork()
        }
    }
    /// В этом случае на экран выведуться все 3 принта