Возникли проблемы с обработчиками завершения и замыканиями в Swift - PullRequest
0 голосов
/ 27 февраля 2020

Фон

Функция ниже вызывает две функции, которые обращаются к API, извлекают данные JSON, анализируют их и т. Д. c, а затем принимают эти данные и заполняет значения переменной объекта в моем классе View Controller.

func requestWordFromOxfordAPI(word: String, completion: (_ success: Bool) -> Void) {
        oxfordAPIManager.fetchDictData(word: word)
        oxfordAPIManager.fetchThesData(word: word)

        completion(true)
    }

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

Например:

Здесь я выбираю данные из базы данных Firebase и, если извлечение данных прошло успешно, я вызываю self.delegate?.populateWordDataFromFB(result: combinedModel). Поскольку замыкания происходят в отдельном потоке, это гарантирует, что функция populateWordDataFromFB выполняется только после завершения извлечения данных. Пожалуйста, поправьте меня, если я ошибаюсь. Я только недавно узнал об этом и все еще пытаюсь увидеть всю картину.

    func readData(word: String) {

        let docRef = db.collection(K.FBConstants.dictionaryCollectionName).document(word)

        docRef.getDocument { (document, error) in
            let result = Result {
                try document.flatMap {
                    try $0.data(as: CombinedModel.self)
                }
            }
            switch result {
            case .success(let combinedModel):
                if let combinedModel = combinedModel {
                    self.delegate?.populateWordDataFromFB(result: combinedModel)
                } else {
                    self.delegate?.fbDidFailWithError(error: nil, summary: "\(word) not found, requesting from OxfordAPI")
                    self.delegate?.requestWordFromOxfordAPI(word: word, completion: { (success) in
                        if success {
                            self.delegate?.populateWordDataFromOX()
                        } else {print("error with completion handler")}
                    })
                }
            case .failure(let error):
                self.delegate?.fbDidFailWithError(error: error, summary: "Error decoding CombinedModel")
            }
        }
    }

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

self.delegate?.requestWordFromOxfordAPI(word: word, completion: { (success) in
                        if success {
                            self.delegate?.populateWordDataFromOX()
                        } else {print("error with completion handler")}
                    })

Моя проблема

С чем я борюсь, так это с тем, что обе функции oxfordAPIManager.fetchDictData(word: word) и oxfordAPIManager.fetchThesData(word: word) имеют замыкания.

Тело этих функций выглядит следующим образом:

        if let url = URL(string: urlString) {
            var request = URLRequest(url: url)
            request.addValue(K.APISettings.acceptField, forHTTPHeaderField: "Accept")
            request.addValue(K.APISettings.paidAppID , forHTTPHeaderField: "app_id")
            request.addValue(K.APISettings.paidAppKey, forHTTPHeaderField: "app_key")

            let session = URLSession.shared
            _ = session.dataTask(with:request) { (data, response, error) in
                if error != nil {
                    self.delegate?.apiDidFailWithError(error: error, summary: "Error performing task:")
                    return
                }

                if let safeData = data {
                    if let thesaurusModel = self.parseThesJSON(safeData) {
                        self.delegate?.populateThesData(thesModel: thesaurusModel, word: word)
                    }
                }
            }
            .resume()
        }  else {print("Error creating thesaurus request")}

Я полагаю, что обе эти функции работают в отдельных потоках в фоновом режиме. Моя цель - вызвать другую функцию после запуска обеих функций oxfordAPIManager.fetchDictData(word: word) и oxfordAPIManager.fetchThesData(word: word). Эти две функции будут заполнять значения объектной переменной в моем контроллере представления, которые я буду использовать в новой функции. Я не хочу, чтобы новая функция вызывалась до того, как переменная объекта в контроллере представления будет заполнена правильными данными, поэтому я попытался реализовать обработчик завершения. Функция обработчика завершения вызывается BEFORE , две функции завершаются, поэтому, когда новая функция пытается получить доступ к переменной объекта в View Controller, она пуста.

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

1 Ответ

2 голосов
/ 27 февраля 2020

Используйте DispatchGroup для этого,

Пример:

Создайте DispatchGroup,

let group = DispatchGroup()

Измените requestWordFromOxfordAPI(word: completion:) метод to,

func requestWordFromOxfordAPI(word: String, completion: @escaping (_ success: Bool) -> Void) {
    fetchDictData(word: "")
    fetchThesData(word: "")
    group.notify(queue: .main) {
        //code after both methods are executed
        print("Both methods executed")
        completion(true)
    }
}

Вызов enter() и leave() методов DispatchGroup в соответствующих местах в методах fetchDictData(word:) и fetchThesData(word:).

func fetchDictData(word: String) {
    group.enter()
    URLSession.shared.dataTask(with: url) { (data, response, error) in
        //your code
        group.leave()
    }.resume()
}

func fetchThesData(word: String) {
    group.enter()
    URLSession.shared.dataTask(with: url) { (data, response, error) in
        //your code
        group.leave()
    }.resume()
}

Наконец Звоните requestWordFromOxfordAPI(word: completion:)

requestWordFromOxfordAPI(word: "") { (success) in
    print(success)
}
...