Быстрое закрытие асинхронный порядок выполнения

#swift #asynchronous #closures

#быстрый #асинхронный #замыкания

Вопрос:

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

 func fetchMostRecent(completion: (sortedSections: [TableItem]) -> ()) {

        self.addressBook.loadContacts({
            (contacts: [APContact]?, error: NSError?) in
            // 1
            if let unwrappedContacts = contacts {
                for contact in unwrappedContacts {

                    // handle constacts
                    ...                        
                    self.mostRecent.append(...)
                }
            }
            // 2
            completion(sortedSections: self.mostRecent)
        })
}
  

Он вызывает другую функцию, которая выполняет асинхронную загрузку контактов, на которую я перенаправляю свое завершение

Вызов fetchMostRecent с завершением выглядит следующим образом:

 model.fetchMostRecent({(sortedSections: [TableItem]) in
    dispatch_async(dispatch_get_main_queue()) {
        // update some UI
        self.state = State.Loaded(sortedSections)
        self.tableView.reloadData()
    }
})
  

Иногда это работает, но очень часто порядок выполнения не такой, как я ожидал. Проблема в том, что иногда completion() under // 2 выполняется до завершения области if under // 1 .

Почему это так? Как я могу гарантировать, что выполнение // 2 запускается после // 1 ?

Комментарии:

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

2. Я понимаю. Необходим асинхронный вызов, поэтому я попробую отправить группу. Спасибо.

Ответ №1:

Пара замечаний:

  1. Он всегда будет выполнять то, что находится на уровне 1 перед 2. Единственный способ получить описываемое вами поведение — это если вы делаете что-то еще внутри этого цикла for, который сам по себе является асинхронным. И если бы это было так, вы бы использовали группу отправки для решения этой проблемы (или реорганизовали код для обработки асинхронного шаблона). Но, не видя, что находится в этом цикле for , трудно комментировать дальше. Сам по себе код в вопросе не должен проявлять проблему, которую вы описываете. Это должно быть что-то еще.

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

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

  4. Обратите внимание, поскольку вы выполняете что-то асинхронное внутри for цикла, вам не только нужно использовать группу отправки для запуска завершения этих асинхронных задач, но вам, вероятно, также необходимо создать свою собственную очередь синхронизации (вы не должны изменять массив из нескольких потоков). Итак, вы можете создать очередь для этого.

Собрав все это вместе, вы получите что-то вроде:

 func fetchMostRecent(completionHandler: ([TableItem]?) -> ()) {
    addressBook.loadContacts { contacts, error in
        var sections = [TableItem]()
        let group = dispatch_group_create()
        let syncQueue = dispatch_queue_create("com.domain.app.sections", nil)

        if let unwrappedContacts = contacts {
            for contact in unwrappedContacts {
                dispatch_group_enter(group)
                self.someAsynchronousMethod {
                    // handle contacts
                    dispatch_async(syncQueue) {
                        let something = ...
                        sections.append(something)
                        dispatch_group_leave(group)
                    }
                }
            }
            dispatch_group_notify(group, dispatch_get_main_queue()) {
                self.mostRecent = sections
                completionHandler(sections)
            }
        } else {
            completionHandler(nil)
        }
    }
}
  

И

 model.fetchMostRecent { sortedSections in
    guard let sortedSections = sortedSections else {
        // handle failure however appropriate for your app
        return
    }

    // update some UI
    self.state = State.Loaded(sortedSections)
    self.tableView.reloadData()
}
  

Или, в Swift 3:

 func fetchMostRecent(completionHandler: @escaping ([TableItem]?) -> ()) {
    addressBook.loadContacts { contacts, error in
        var sections = [TableItem]()
        let group = DispatchGroup()
        let syncQueue = DispatchQueue(label: "com.domain.app.sections")

        if let unwrappedContacts = contacts {
            for contact in unwrappedContacts {
                group.enter()
                self.someAsynchronousMethod {
                    // handle contacts
                    syncQueue.async {
                        let something = ...
                        sections.append(something)
                        group.leave()
                    }
                }
            }
            group.notify(queue: .main) {
                self.mostRecent = sections
                completionHandler(sections)
            }
        } else {
            completionHandler(nil)
        }
    }
}