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

Posted

技术标签:

【中文标题】在 Swift 中遇到完成处理程序和闭包问题【英文标题】:Having Trouble With Completion Handlers and Closures in Swift 【发布时间】:2020-02-27 08:12:28 【问题描述】:

背景

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

这是我第一次尝试实现完成处理程序,我尝试关注其他一些堆栈溢出帖子,但没有成功。另外,如果这是错误的方法,请也告诉我。很抱歉解释太长,感谢您的任何意见。

【问题讨论】:

我认为您错误地实现了requestWordFromOxfordAPI。您的问题是在问如何正确实施它吗?可以换oxfordAPIManager.fetchDictData吗? 【参考方案1】:

为此使用 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)
    

fetchDictData(word:)fetchThesData(word:)方法的相关位置调用DispatchGroupenter()leave()方法。

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)

【讨论】:

以上是关于在 Swift 中遇到完成处理程序和闭包问题的主要内容,如果未能解决你的问题,请参考以下文章

Swift 闭包完成处理程序

swift - 如何从系统函数的完成处理程序闭包中返回?

如何存储闭包完成处理程序以供以后调用?

Swift 完成处理程序 - 转义尾随闭包

iOS Swift 如何访问在完成处理程序闭包中创建的数据——在闭包之外

当完成处理程序显式使用 @escaping 时,Swift 推断完成处理程序闭包是默认的 @nonescaping 而不是 @escaping