在Swift中遇到完成处理程序和闭包问题

时间:2020-02-27 08:12:28

标签: ios swift closures completionhandler

背景

下面的函数调用两个函数,这两个函数均访问API,检索JSON数据,通过其进行解析等,然后获取该数据并填充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")
            }
        }
    }

还要从上面的代码中注意到,如果数据不在Firebase中,我将调用下面的委托方法,这是我遇到问题的地方。

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)函数都运行后调用另一个函数。这两个函数将在我的视图控制器中填充对象变量的值,该变量将在新函数中使用。我不希望在使用正确的数据填充视图控制器中的对象变量之前调用新函数,所以我尝试实现完成处理程序。完成处理程序函数被称为之前,这两个函数终止,因此,当新函数尝试访问视图控制器中的对象变量时,它为空。

这是我第一次尝试实现完成处理程序,我尝试关注其他一些堆栈溢出帖子,但未成功。如果这是错误的方法,也请让我知道。抱歉,冗长的解释,谢谢您的投入。

1 个答案:

答案 0 :(得分:2)

为此使用 DispatchGroup

示例:

创建一个DispatchGroup

let group = DispatchGroup()

requestWordFromOxfordAPI(word: completion:)方法修改为

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的{​​{1}}和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:)