Вопрос по asynchronous, closures, swift – Быстрое закрытие, асинхронный порядок выполнения

1

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

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() под// 2 выполняется до объемаif под// 1 было закончено

Это почему? Как я могу обеспечить выполнение// 2 начинается после// 1?

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

Ваш Ответ

1   ответ
3

Пара наблюдений:

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

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

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

Обратите внимание, поскольку вы делаете что-то асинхронное внутри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)
        }
    }
}
Работает :) еще раз спасибо Adam Bardon

Похожие вопросы