Асинхронная функция с DispatchGroup - PullRequest
0 голосов
/ 03 августа 2020

Я пытаюсь установить анимацию внутри Playgrounds и сделать простое сообщение, всплывающее в отладчике после завершения.

При этом .notify появляется в начале. Разве это не появилось бы после того, как функции анимации прошли внутри DispatchQueue.main.asyn c (group: animationGroup)?

Вот предоставленный код:

import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

extension UIView {
  static func animate(withDuration duration: TimeInterval, animations: @escaping () -> Void, group: DispatchGroup, completion: ((Bool) -> Void)?) {
     
  }
}
let animationGroup = DispatchGroup()
// A red square
let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
view.backgroundColor = UIColor.red
// A yellow box
let box = UIView(frame: CGRect(x: 0, y: 0, width: 40, height: 40))
box.backgroundColor = UIColor.yellow
view.addSubview(box)
PlaygroundPage.current.liveView = view

DispatchQueue.main.async(group: animationGroup) {

  UIView.animate(withDuration: 1, animations: {
    // Move box to lower right corner
    box.center = CGPoint(x: 150, y: 150)
    }, completion: {
      _ in
      UIView.animate(withDuration: 2, animations: {
        // Rotate box 45 degrees
        box.transform = CGAffineTransform(rotationAngle: .pi/4)
        }, completion: .none)
  })

  UIView.animate(withDuration: 4, animations: { () -> Void in
    // Change background color to blue
    view.backgroundColor = UIColor.blue
  })
}

animationGroup.notify(queue: DispatchQueue.main) {
  print("Animations Completed!")
}

Ответы [ 2 ]

0 голосов
/ 03 августа 2020

Вызов async(group:os:flags:execute:) API, который вы используете, используется при выполнении задач, которые сами по себе являются синхронными. Например, предположим, что вы собирались обрабатывать кучу изображений в каком-то медленном синхронном API, но хотели запустить это параллельно, в фоновых потоках, чтобы избежать блокировки основного потока. Тогда вы должны использовать async(group:execute:) API:

let group = DispatchGroup()
for image in images {
    DispatchQueue.global().async(group: group) {
        self.process(image)
    }
}

group.notify(queue: .main) {
    // all done processing the images
}

Однако в этом случае отдельные вызовы API анимации на основе блоков UIView выполняются асинхронно. Таким образом, вы не можете использовать этот шаблон async(group:execute:). Вы должны вручную enter перед запуском каждого асинхронного процесса, а затем leave внутри соответствующих закрытий завершения, сопоставив их один к одному:

animationGroup.enter()

UIView.animate(withDuration: 1, animations: {
    box.center = CGPoint(x: 150, y: 150)                           // Move box to lower right corner
}, completion: { _ in
    UIView.animate(withDuration: 2, animations: {
        box.transform = CGAffineTransform(rotationAngle: .pi / 4)  // Rotate box 45 degrees
    }, completion: { _ in
        animationGroup.leave()
    })
})

animationGroup.enter()

UIView.animate(withDuration: 4, animations: {
    view.backgroundColor = .blue                                   // Change background color to blue
}, completion: { _ in
    animationGroup.leave()
})

animationGroup.notify(queue: .main) {
    print("Animations Completed!")
}
0 голосов
/ 03 августа 2020

Прежде всего, процитируем документацию :

notify (queue: work:)

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

В основном, нет «ранее» отправленных блоков, поэтому ваш notify блок вызывается сразу. Это происходит потому, что вы добавили рабочие элементы в группу в DispatchQueue.main.async. Вы можете проверить это, добавив печать внутри DispatchQueue.main.async(group: animationGroup) и еще одну печать прямо перед вызовом метода notify. Второй будет напечатан первым.

Кроме того, вызов асинхронной функции внутри DispatchGroup.main.async является проблематичным c, потому что рабочий элемент войдет немедленно, тогда вы отправите свою асинхронную c работу в другой поток, и рабочий элемент уйдет сразу после этого, даже если ваша работа asyn c не выполнена.

Чтобы исправить это, вы должны вызвать animationGroup.enter() при запуске асинхронной операции, а затем позвоните animationGroup.leave(), когда закончите. Дополнительно вы должны вывести свой код из DispatchGroup.main.async. Если вы внесете эти изменения, блок notify будет выполняться, когда вызовы enter/leave сбалансированы.

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