Grand Central Dispatch или GCD - это способ справиться с многозадачностью в наших приложениях для iOS. Принцип работы этой системы заключается в том, что ряд задач может быть отправлен в очереди отправки, которые, в свою очередь, будут выполняться в нескольких потоках и в конечном итоге будут управляться системой.

Часто мы думаем о GCD, когда пытаемся обновить пользовательский интерфейс в одном из наших приложений. Эти обновления происходят в основном потоке, однако есть и другие задачи, которые нам могут потребоваться, и они могут выполняться в параллельном или фоновом потоке.

Чтобы добавить некоторый контекст, очередь - это блок кода, который может выполняться синхронно или асинхронно в основном или фоновом потоке.

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

Чтобы лучше понять, что означают некоторые из этих концепций, давайте сразу перейдем к коду и создадим очередь.

let queue = DispatchQueue(label: “queue.1”)

Здесь мы создали настраиваемую очередь и присвоили ей уникальный ярлык. То, что вы называете этим ярлыком, совершенно произвольно, но лучше назвать его чем-то, имеющим отношение к вашему приложению.

Мы можем вызывать в этих очередях разные методы, такие как async vs sync. Эти ключевые слова сообщают нашему приложению, как выполнять наш код.

Вот пример того, как наш код работает синхронно в фоновом потоке по сравнению с кодом, который выполняется в основном потоке.

// Background thread
queue.sync {
     for i in 0..<10 {
          print("🔷", i)
     }
}
// Main thread
for i in 20..<30 {
     print("⚪️", i)
}

Если мы запустим этот код, мы увидим что-то вроде этого:

// prints: 🔷 0🔷 1🔷 2🔷 3🔷 4...⚪️ 20⚪️ 21⚪️ 22⚪️ 23⚪️ 24...

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

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

// prints: 🔷 0⚪️ 20🔷 1⚪️ 21🔷 2⚪️ 22🔷 3⚪️ 23🔷 4⚪️ 24🔷 ...

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

.userInteractive
.userInitiated
.default
.utility
.background
.unspecified

Давайте попробуем и сравним две очереди, изменив их qos.

let firstQueue= DispatchQueue(label: "queue1", qos: DispatchQoS.userInitiated)
let secondQueue = DispatchQueue(label: "queue2", qos: DispatchQoS.userInitiated)
firstQueue.sync {
     for i in 0..<10 {
          print("🔷", i)
     }
}
secondQueue.sync {
    for i in 20..<30 {
         print("⚪️", i)
     }
}

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

// prints: ⚪️ 20🔷 0⚪️ 21🔷 1⚪️ 22🔷 2⚪️ 23🔷 3⚪️ 24🔷 4 ...

Если вы попытаетесь изменить secondQueue qos на .background, вы получите совсем другой результат, который будет напоминать что-то вроде этого:

// prints: 🔷 0🔷 1🔷 2🔷 3🔷 4...⚪️ 20⚪️ 21⚪️ 22⚪️ 23⚪️ 24...

Наш firstQueue имеет более высокий рейтинг с точки зрения qos, поэтому наше приложение будет отдавать приоритет этой задаче и следить за тем, чтобы она выполнялась до secondQueue.

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

Давайте взглянем на наши последовательные очереди, и на этот раз мы назначим им множество задач.

let randomQueue = DispatchQueue(label: "randomQueue", qos: .utility)

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

randomQueue.async {
     for i in 0..<10 {
          print("🔷", i)
     }
}
randomQueue.async {
     for i in 20..<30 {
         print("⚪️", i)
     }
}
randomQueue.async {
     for i in 30..<40 { 
          print("🔶", i)
     }
}
// prints: 🔷 0🔷 1🔷 2🔷 3🔷 4...⚪️ 20⚪️ 21⚪️ 22⚪️ 23⚪️ 24...🔶 30🔶 31🔶 32🔶 33

Здесь мы видим, что наша очередь выполняет каждую задачу по очереди.

Однако, чтобы создать параллельную очередь, мы добавим attribute к нашему randomQueue.

let randomQueue = DispatchQueue(label: "randomQueue", qos: .utility, attributes: .concurrent)

Если мы запустим наше приложение, мы заметим, что наши задачи теперь выполняются параллельно.

// prints: 🔹 0⚪️ 20🔶 30🔹 1⚪️ 21🔶 31🔹 2⚪️ 22🔶 32🔹 3⚪️ 23🔶 33...

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

let globalQueue = DispatchQueue.global()
globalQueue.async {
     // Do something here
}

Глобальные очереди можно использовать так же, как мы используем пользовательские очереди, но ими нельзя злоупотреблять. Есть определенные черты, которые мы не можем настроить с помощью этой очереди, но мы всегда можем изменить qos.

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

DispatchQueue.main.async {
     // Do something here
}

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

В целом GCD позволяет нам выполнять многозадачность в наших приложениях и запускать код, который является синхронным или асинхронным, а также последовательным или одновременным, в зависимости от того, как мы хотим, чтобы наше приложение выполняло определенные блоки кода. Мы можем создавать собственные очереди или использовать глобальные очереди Apple для выполнения этих различных задач, и основной поток всегда должен быть посвящен обновлению пользовательского интерфейса.

Надеюсь, этот пост был полезен, и не стесняйтесь оставлять комментарии ниже!