Учебное пособие по Grand Central Dispatch для Swift 5: часть 2/2

Узнайте все о многопоточности, очередях отправки и параллелизме во второй части этого руководства по Swift 5 на Grand Central Dispatch.

Примечание об обновлении: Дэвид Пайпер обновил это руководство для iOS 15, Swift 5.5 и Xcode 13. Кристин Абернати написала оригинал.

Добро пожаловать во вторую и последнюю часть этой серии руководств по Grand Central Dispatch!

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

В этом втором руководстве по Grand Central Dispatch вы будете работать с тем же приложением GooglyPuff, которое вы знаете и любите из первой части. Вы углубитесь в продвинутые концепции GCD, в том числе:

  • Диспетчерские группы
  • Отмена блоков рассылки
  • Методы асинхронного тестирования
  • Источники отправки

Пришло время изучить еще немного GCD!

Начиная

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

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

Запустите приложение, нажмите + и выберите Le Internet, чтобы добавить фотографии из Интернета. Вы можете заметить, что предупреждающее сообщение о завершении загрузки появляется задолго до завершения загрузки изображений:

Это первое, что вы будете исправлять.

Использование Dispatch Groups

Откройте PhotoManager.swift и проверьте downloadPhotos(withCompletion:):

func downloadPhotos(
  withCompletion completion: BatchPhotoDownloadingCompletionClosure?
) {
  var storedError: NSError?
  for address in [
    PhotoURLString.overlyAttachedGirlfriend,
    PhotoURLString.successKid,
    PhotoURLString.lotsOfFaces
  ] {
    guard let url = URL(string: address) else { return }
    let photo = DownloadPhoto(url: url) { _, error in
      storedError = error
    }
    PhotoManager.shared.addPhoto(photo)
  }

  completion?(storedError)
}

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

Начните загрузку фотографий, вызвав DownloadPhoto(url:). Этот вызов возвращается немедленно, но фактическая загрузка происходит асинхронно. Таким образом, когда завершение выполняется, нет гарантии, что все загрузки завершены.

Вы хотите, чтобы downloadPhotos(withCompletion:) вызывал закрытие завершения после завершения всех задач загрузки фотографий. Как вы можете отслеживать эти параллельные асинхронные события, чтобы добиться этого? С текущим методом вы не знаете, когда задачи будут завершены, и они могут завершиться в любом порядке.

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

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

В PhotoManager.swift замените код в downloadPhotos(withCompletion:) следующим:

// 1
DispatchQueue.global(qos: .userInitiated).async {
  var storedError: NSError?

  // 2
  let downloadGroup = DispatchGroup()
  for address in [
    PhotoURLString.overlyAttachedGirlfriend,
    PhotoURLString.successKid,
    PhotoURLString.lotsOfFaces
  ] {
    guard let url = URL(string: address) else { return }

    // 3
    downloadGroup.enter()
    let photo = DownloadPhoto(url: url) { _, error in
      storedError = error

      // 4
      downloadGroup.leave()
    }   
    PhotoManager.shared.addPhoto(photo)
  }   

  // 5      
  downloadGroup.wait()

  // 6
  DispatchQueue.main.async {
    completion?(storedError)
  }   
}

Вот что делает код шаг за шагом:

  1. Метод синхронного ожидания блокирует текущий поток. Таким образом, вам нужно использовать асинхронность, чтобы поместить весь метод в фоновую очередь. Это гарантирует, что вы не заблокируете основной поток.
  2. Создайте новую группу рассылки.
  3. Вызовите enter(), чтобы вручную уведомить группу о запуске задачи. Вы должны сбалансировать количество вызовов enter() с количеством вызовов leave(), иначе ваше приложение выйдет из строя.
  4. Сообщите группе, что работа выполнена.
  5. Вызовите wait(), чтобы заблокировать текущий поток в ожидании завершения задач. Это ждет вечно — и это нормально, потому что задача создания фотографий всегда завершается. Вы можете использовать wait(timeout:), чтобы указать время ожидания и выйти из режима ожидания по истечении указанного времени.
  6. На данный момент вы знаете, что все задачи изображения либо завершены, либо истекло время ожидания. Затем вы делаете обратный вызов в основную очередь, чтобы выполнить закрытие завершения.

Создайте и запустите приложение. Загрузите фотографии через опцию Le Internet и убедитесь, что предупреждение не появляется, пока все изображения не будут загружены.

Примечание. Сетевые действия могут происходить слишком быстро, чтобы понять, когда следует вызвать закрытие завершения. Если вы запускаете приложение на устройстве, убедитесь, что оно действительно работает. Вам нужно переключить некоторые сетевые настройки в разделе «Разработчик» в настройках iOS. Перейдите в раздел Network Link Conditioner, включите его и выберите профиль. Очень плохая сеть — хороший выбор.

Если вы работаете в iOS Simulator, используйте Network Link Conditioner, включенный в Advanced Tools for Xcode, чтобы изменить скорость вашей сети. Это хороший инструмент, который должен быть в вашем арсенале. Это заставляет вас осознавать, что происходит с вашими приложениями, когда скорость соединения ниже оптимальной.

Диспетчерские группы являются хорошим кандидатом для всех типов очередей. Следует с осторожностью использовать группы отправки в основной очереди, если вы ожидаете синхронного завершения всей работы. Вы же не хотите задерживать основной поток, не так ли? ;] Асинхронная модель — это привлекательный способ обновления пользовательского интерфейса после завершения нескольких длительных задач, таких как сетевые вызовы.

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

Используя Dispatch Groups, Take 2

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

По-прежнему в PhotoManager.swift замените код внутри downloadPhotos(withCompletion:) следующим:

// 1
var storedError: NSError?
let downloadGroup = DispatchGroup()
for address in [
  PhotoURLString.overlyAttachedGirlfriend,
  PhotoURLString.successKid,
  PhotoURLString.lotsOfFaces
] {
  guard let url = URL(string: address) else { return }
  downloadGroup.enter()
  let photo = DownloadPhoto(url: url) { _, error in
    storedError = error
    downloadGroup.leave()
  }   
  PhotoManager.shared.addPhoto(photo)
}   

// 2    
downloadGroup.notify(queue: DispatchQueue.main) {
  completion?(storedError)
}

Вот что происходит:

  1. На этот раз вам не нужно помещать метод в асинхронный вызов, поскольку вы не блокируете основной поток.
  2. notify(queue:work:) служит асинхронным закрытием завершения. Он запускается, когда в группе больше не осталось элементов. Вы также указываете, что хотите запланировать выполнение работы завершения в основной очереди.

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

Создайте и запустите приложение. Убедитесь, что предупреждение о завершении загрузки по-прежнему отображается после загрузки всех фотографий из Интернета:

Изучение циклов Concurrency

Имея в своем распоряжении все эти новые инструменты, вам, вероятно, следует нарезать все, верно?!

Взгляните на downloadPhotos(withCompletion:) в PhotoManager. Вы могли заметить, что там есть цикл for, который проходит через три итерации и загружает три отдельных изображения. Ваша задача — посмотреть, сможете ли вы запустить этот цикл for одновременно, чтобы попытаться ускорить процесс.

Это задание для DispatchQueue.concurrentPerform(iterations:execute:). Он работает как цикл for, поскольку одновременно выполняет разные итерации. Он синхронный и возвращается только тогда, когда вся работа выполнена.

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

Когда целесообразно использовать DispatchQueue.concurrentPerform(iterations:execute:)? Вы можете исключить последовательные очереди, потому что в этом нет никакой выгоды — вы можете также использовать обычный цикл for. Тем не менее, это хороший выбор для параллельных очередей, которые содержат циклы, особенно если вам нужно отслеживать прогресс.

В PhotoManager.swift замените код внутри downloadPhotos(withCompletion:) следующим:

var storedError: NSError?
let downloadGroup = DispatchGroup()
let addresses = [
  PhotoURLString.overlyAttachedGirlfriend,
  PhotoURLString.successKid,
  PhotoURLString.lotsOfFaces
]
let _ = DispatchQueue.global(qos: .userInitiated)
DispatchQueue.concurrentPerform(iterations: addresses.count) { index in
  let address = addresses[index]
  guard let url = URL(string: address) else { return }
  downloadGroup.enter()
  let photo = DownloadPhoto(url: url) { _, error in
    storedError = error
    downloadGroup.leave()
  }
  PhotoManager.shared.addPhoto(photo)
}
downloadGroup.notify(queue: DispatchQueue.main) {
  completion?(storedError)
}

Вы заменили прежний цикл for на DispatchQueue.concurrentPerform(iterations:execute:) для обработки параллельного цикла.

Эта реализация включает любопытную строку кода: let _ = DispatchQueue.global(qos: .userInitiated). Это заставляет GCD использовать очередь с качеством обслуживания .userInitiated для одновременных вызовов.

Создайте и запустите приложение. Убедитесь, что функция загрузки из Интернета по-прежнему работает правильно:

Запуск этого нового кода на устройстве иногда дает несколько более быстрые результаты. Но стоила ли вся эта работа того?

На самом деле, в данном случае это того не стоит. Вот почему:

  • Вы, вероятно, создали больше накладных расходов, запуская потоки параллельно, чем если бы просто запустили цикл for. Вы должны использовать DispatchQueue.concurrentPerform(iterations:execute:) для перебора очень больших наборов вместе с соответствующей длиной шага.
  • У вас есть ограниченное время для создания приложения — не тратьте время на предварительную оптимизацию кода, о котором вы не знаете, что он неисправен. Если вы собираетесь что-то оптимизировать, делайте это с тем, что заметно и стоит вашего времени. Найдите методы с наибольшим временем выполнения, профилируя свое приложение в инструментах. Ознакомьтесь с разделом «Как использовать инструменты в Xcode», чтобы узнать больше.
  • Как правило, оптимизация кода усложняет ваш код для вас самих и для других разработчиков, которые придут после вас. Убедитесь, что дополнительное усложнение стоит выгоды.

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

Отмена Dispatch Blocks

До сих пор вы не видели кода, позволяющего отменять поставленные в очередь задачи. Именно здесь объекты блока отправки, представленные DispatchWorkItem, становятся в центре внимания. Имейте в виду, что вы можете отменить DispatchWorkItem только до того, как он достигнет начала очереди и начнет выполняться.

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

В PhotoManager.swift замените код в downloadPhotos(withCompletion:) следующим:

var storedError: NSError?
let downloadGroup = DispatchGroup()
var addresses = [
  PhotoURLString.overlyAttachedGirlfriend,
  PhotoURLString.successKid,
  PhotoURLString.lotsOfFaces
]

// 1
addresses += addresses + addresses

// 2
var blocks: [DispatchWorkItem] = []

for index in 0..<addresses.count {
  downloadGroup.enter()

  // 3
  let block = DispatchWorkItem(flags: .inheritQoS) {
    let address = addresses[index]
    guard let url = URL(string: address) else {
      downloadGroup.leave()
      return
    }
    let photo = DownloadPhoto(url: url) { _, error in
      storedError = error
      downloadGroup.leave()
    }
    PhotoManager.shared.addPhoto(photo)
  }
  blocks.append(block)

  // 4
  DispatchQueue.main.async(execute: block)
}

// 5
for block in blocks[3..<blocks.count] {

  // 6
  let cancel = Bool.random()
  if cancel {

    // 7
    block.cancel()

    // 8
    downloadGroup.leave()
  }
}

downloadGroup.notify(queue: DispatchQueue.main) {
  completion?(storedError)
}

Вот пошаговое выполнение приведенного выше кода:

  1. Вы расширяете массив адресов, чтобы содержать три копии каждого изображения.
  2. Вы инициализируете массив блоков для хранения объектов блока отправки для последующего использования.
  3. Вы создаете новый DispatchWorkItem. Вы передаете параметр flags, чтобы указать, что блок должен наследовать свой класс Quality of Service из очереди, в которую вы его отправляете. Затем вы определяете работу, которую необходимо выполнить в замыкании.
  4. Вы отправляете блок асинхронно в основную очередь. В этом примере использование основной очереди упрощает отмену выбранных блоков, поскольку это последовательная очередь. Код, устанавливающий блоки отправки, уже выполняется в основной очереди. Таким образом, вы знаете, что блоки загрузки будут выполняться через некоторое время.
  5. Вы пропускаете первые три блока загрузки, разрезая массив блоков.
  6. Здесь вы используете Bool.random() для случайного выбора между истинным и ложным. Это как подбрасывание монеты.
  7. Если случайное значение истинно, вы отменяете блокировку. Это может отменить только те блоки, которые все еще находятся в очереди и еще не начали выполняться. Вы не можете отменить блок в середине выполнения.
  8. Здесь вы не забудьте удалить отмененный блок из группы отправки.

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

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

Блоки отправки могут делать гораздо больше, поэтому обязательно ознакомьтесь с документацией Apple.

Разное GCD

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

Тестирование асинхронного кода

Это может показаться сумасшедшей идеей, но знаете ли вы, что в Xcode есть функция тестирования? :] Написание и запуск тестов важны при построении сложных взаимосвязей в коде.

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

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

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

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

Использование семафоров

Семафоры — это концепция многопоточности старой школы, представленная миру очень скромным Эдсгером В. Дейкстрой. Семафоры — сложная тема, потому что они основаны на сложностях функций операционной системы.

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

Откройте GooglyPuffTests.swift и замените код внутри downloadImageURL(withString:) следующим:

let url = try XCTUnwrap(URL(string: urlString))

// 1
let semaphore = DispatchSemaphore(value: 0)
_ = DownloadPhoto(url: url) { _, error in
  if let error = error {
    XCTFail("\(urlString) failed. \(error.localizedDescription)")
  }

  // 2
  semaphore.signal()
}
let timeout = DispatchTime.now() + .seconds(defaultTimeoutLengthInSeconds)

// 3
if semaphore.wait(timeout: timeout) == .timedOut {
  XCTFail("\(urlString) timed out")
} 

Вот как работает семафор в приведенном выше коде:

  1. Вы создаете семафор и устанавливаете его начальное значение. Это представляет количество вещей, которые могут получить доступ к семафору без необходимости его увеличения. Другое название увеличения семафора — его сигнализация.
  2. Вы сигнализируете семафору в закрытии завершения. Это увеличивает его счетчик и сигнализирует, что семафор доступен для других ресурсов.
  3. Вы ждете на семафоре с заданным тайм-аутом. Этот вызов блокирует текущий поток, пока вы не просигнализируете семафор. Ненулевой код возврата этой функции означает, что время ожидания истекло. В этом случае тест не пройден, потому что сеть не должна возвращаться более чем за 10 секунд — справедливое замечание!

Запустите тесты, выбрав Продукт ▸ Тест в меню или нажав Command-U, если у вас есть привязки клавиш по умолчанию. Все они должны преуспеть:

Отключите соединение и снова запустите тесты. Если вы работаете на устройстве, переведите его в авиарежим. Если вы работаете в симуляторе, отключите соединение. Тесты завершаются с отказом через 10 секунд. Это сработало!

Это довольно тривиальные тесты. Но если вы работаете с серверной командой, они могут предотвратить поиск виноватых в последней проблеме с сетью.

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

Использование источников отправки

Источники отправки — особенно интересная особенность GCD. Вы можете использовать источник отправки для отслеживания некоторых типов событий. События могут включать в себя сигналы Unix, дескрипторы файлов, порты Mach, узлы VFS и другие неясные вещи.

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

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

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

Откройте PhotoCollectionViewController.swift и добавьте следующее сразу после объявления глобального свойства backgroundImageOpacity:

// 1
#if DEBUG

  // 2
  var signal: DispatchSourceSignal?

  // 3
  private let setupSignalHandlerFor = { (_ object: AnyObject) in
    let queue = DispatchQueue.main

    // 4
    signal =
      DispatchSource.makeSignalSource(signal: SIGSTOP, queue: queue)
        
    // 5
    signal?.setEventHandler {
      print("Hi, I am: \(object.description ?? "")")
    }

    // 6
    signal?.resume()
  }
#endif

Код немного сложный, поэтому пройдемся по нему шаг за шагом:

  1. Вы компилируете этот код только в режиме отладки, чтобы не дать «заинтересованным сторонам» получить больше информации о вашем приложении. :] Добавьте -D DEBUG в разделе «Настройки проекта» ▸ Настройки сборки ▸ Компилятор Swift — Пользовательские флаги ▸ Другие флаги Swift ▸ Отладка. Он должен быть установлен уже в стартовом проекте.
  2. Объявите сигнальную переменную типа DispatchSourceSignal для использования при мониторинге сигналов Unix.
  3. Создайте блок, назначенный глобальному свойству setupSignalHandlerFor. Вы используете его для одноразовой настройки вашего источника отправки.
  4. Здесь вы настраиваете сигнал. Вы заинтересованы в мониторинге сигнала SIGSTOP Unix. В основной очереди раздаются события — вскоре вы поймете, почему.
  5. Затем вы регистрируете закрытие обработчика событий, которое вызывается всякий раз, когда вы получаете сигнал SIGSTOP. Ваш обработчик выводит сообщение, включающее описание класса.
  6. Все источники по умолчанию находятся в приостановленном состоянии. Здесь вы указываете источнику отправки возобновить работу, чтобы он мог начать отслеживать события.

Добавьте следующий код в viewDidLoad() после вызова super.viewDidLoad():

#if DEBUG
  setupSignalHandlerFor(self)
#endif

Этот код вызывает код инициализации источника отправки.

Создайте и запустите приложение. Приостановите выполнение программы и немедленно возобновите работу приложения, нажав кнопки «Пауза», а затем «Воспроизвести» в отладчике Xcode:

Проверьте консоль. Вот что вы увидите:

Hi, I am: <GooglyPuff.PhotoCollectionViewController: 0x7fbf0af08a10>

Теперь ваше приложение поддерживает отладку! Это довольно круто, но как бы вы использовали это в реальной жизни?

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

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

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

Поставьте точку останова на оператор print() внутри только что добавленного блока setupSignalHandlerFor.

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

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

В консоли отладчика введите следующее:

expr object.navigationItem.prompt = "WOOT!"

Отладчик Xcode иногда может не сотрудничать. Вы можете получить это сообщение:

error: use of unresolved identifier 'self'

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

po object

Затем вручную приведите значение к нужному типу, выполнив следующие команды в отладчике, заменив 0xHEXADDRESS на выведенный адрес:

expr let $vc = unsafeBitCast(0xHEXADDRESS, to: GooglyPuff.PhotoCollectionViewController.self)
expr $vc.navigationItem.prompt = "WOOT!"

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

После успешного выполнения этой команды возобновите выполнение приложения. Вы увидите следующее:

Начальный контроллер представления, но на этот раз панель навигации показывает текст WOOT!.

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

Куда пойти отсюда?

Вы можете загрузить завершенную версию проекта с помощью кнопки «Загрузить материалы» в верхней или нижней части этого руководства.

Помимо GCD, вам следует ознакомиться с Учебным пособием по Operation и OperationQueue в Swift, технологии параллелизма, построенной на основе GCD. В общем, лучше всего использовать GCD, если вы используете простые задачи типа «выстрелил и забыл». Operation предлагает лучший контроль, реализацию для обработки максимального количества одновременных операций и более объектно-ориентированную парадигму за счет скорости.

Кроме того, взгляните на видеокурс iOS Concurrency with GCD and Operations, который затрагивает множество тем, затронутых в этом руководстве.

Если вы хотите узнать, как использовать async и await в своем коде, ознакомьтесь с нашей книгой Modern Concurrency in Swift.

Помните, что если у вас нет особых причин для перехода на более низкий уровень, всегда старайтесь придерживаться API более высокого уровня. Занимайтесь темными искусствами Apple только в том случае, если вы хотите узнать больше или сделать что-то очень, очень «интересное». :]

Если у вас есть какие-либо вопросы или комментарии, пожалуйста, присоединяйтесь к обсуждению на форуме ниже!