swift中具有多个闭包/ API请求的函数中的异步完成处理

Posted

技术标签:

【中文标题】swift中具有多个闭包/ API请求的函数中的异步完成处理【英文标题】:Asynchronous completion handling in a function with multiple closures/API requests in swift 【发布时间】:2015-06-13 19:48:55 【问题描述】:

我刚开始使用 Swift 进行开发,所以我对闭包完全陌生。我也是如何处理异步 API 请求的新手。

我读过很多类似的问题,例如How to get data to return from NSURLSessionDataTask in Swift 和How to use completionHandler Closure with return in Swift?。这些对我有帮助,但我的问题有点不同。

在我的函数中,我想首先发出 API 请求以获取 JSON 有效负载。有了这个 JSON 有效负载中的一些数据,我想发出多个其他 API 请求。在这种情况下,我将为每个 API 请求接收一个 JSON 有效负载,其中我想将一些数据存储在我自己的 JSON 数据结构中。

问题是,对于我发出的每个多个 API 请求,我只能在 CompletionHandler 中返回我自己的部分 JSON 数据 - 据我所知,这是使用闭包发出 API 请求时返回数据的唯一方法.

因此,在调用我的函数时,我只想接收一个,而不是获取多个完成处理程序。

问题是我不知道如何在一个函数中完成处理多个闭包,在这种情况下是两个闭包。

我在下面发布了我的代码 - 我知道它很长而且可能不是那么干净。 但是,关键是当我将优惠更新到我的 storeDict 时,这将是空的,因为优惠 dict 数组正在从第二个闭包内部获取其信息。这显示在函数的底部。

   func getOffersFromWishList(offerWishList: [String], latitude: Double, longitude: Double, radius: Int, completionHandler: ([NSDictionary] -> Void)) 

        var master: [NSDictionary] = []

        var nearby_params: NSDictionary = ["r_lat": latitude, "r_lng": longitude, "r_radius": radius]
        //println(nearby_params)
        var store_id_list: [String] = []


        // Get all store_ids for store which are nearby (Radius determines how nearby)
        singleton_eta.api("/v2/stores", type: ETARequestTypeGET, parameters: nearby_params, useCache: true, completion:  (response, error, fromCache) -> Void in

            if error == nil 

                let json = JSON(response)
                storeArray = json.arrayValue
                //println(storeArray)

                for store in storeArray 

                    var storeDict = [String: AnyObject]()
                    var metaData = [String: String]()
                    var offers: [NSDictionary] = []

                    let name = store["branding"]["name"].stringValue
                    let store_id = store["id"].stringValue
                    let street = store["street"].stringValue
                    let city = store["city"].stringValue
                    let zip_code = store["zip_code"].stringValue
                    let dealer_id = store["dealer_id"].stringValue
                    let logo = store["branding"]["logo"].stringValue

                    metaData = ["name": name, "store_id": store_id, "street": street, "city": city, "zip_code": zip_code, "dealer_id": dealer_id, "logo": logo]

                    store_id_list.append(store_id)

                    //println("Butiks ID: \(store_id)")

                    var offset = 0
                    let limit = 100

                    // Loop through the offers for the specific store id - only possible to request 100 offers each time
                    // A while loop would be more suitable, but I dont know when to stop, as the length of the offerArray can not be counted as it is cant be accessed outside of the closure.
                    for x in 1...2 

                        var store_params: NSDictionary = ["store_ids:": store_id, "limit": limit, "offset": offset]
                        println(store_params)
                        // Get offers for a specific store_id
                        singleton_eta.api("/v2/offers", type: ETARequestTypeGET, parameters: store_params, useCache: true, completion:  (response, error, fromCache) -> Void in

                            if error == nil 

                                offerArray = JSON(response).arrayValue
                                //println( "TypeName0 = \(_stdlib_getTypeName(offerArray))")

                                //Loop through the recieved offers
                                for of in offerArray 

                                    let name = of["branding"]["name"].stringValue
                                    let dealer_id = of["dealer_id"].stringValue
                                    let heading = of["heading"].stringValue
                                    let description = of["description"].stringValue
                                    let price = of["pricing"]["price"].stringValue
                                    let image = of["images"]["view"].stringValue


                                    //println(heading)

                                    // Loop through our offerWishList
                                    for owl in offerWishList 

                                        let headingContainsWish = (heading.lowercaseString as NSString).containsString(owl.lowercaseString)

                                        // Check if offer match with our wish list
                                        if(headingContainsWish) 

                                            // Save neccesary meta data about each offer to a tuple array
                                            var offer = Dictionary<String, String>()
                                            offer = ["name": name, "dealer_id": dealer_id, "heading": heading, "description": description, "price": price, "image": image, "offerWishItem": owl]

                                            offers.append(offer)


                                        

                                    

                                


                            

                        )
                        //println(storeDict)
                        offset = offset + limit + 1

                    

                    storeDict.updateValue(metaData, forKey: "meta_data")
                    storeDict.updateValue(offers, forKey: "offers")  // offers is empty due to its appending inside the closure
                    master.append(storeDict)

                
             completionHandler(master)

            

            else 
                println(error)
            


        )

    

调用上述函数

getOffersFromWishList(offerWishList, latitude, longitude, radius)  (master) -> Void in
       println(master)
    

这是master在调用函数时会打印的内容,其中offers为空。


"meta_data" =     
    city = "Kongens Lyngby";
    "dealer_id" = d8adog;
    logo = "https://d3ikkoqs9ddhdl.cloudfront.net/img/logo/default/d8adog_3qvn3g8xp.png";
    name = "d\U00f8gnNetto";
    "store_id" = d2283Zm;
    street = "Kollegiebakken 7";
    "zip_code" = 2800;
;
offers =     (
);

 
  ...

所以我的问题是,在函数内将数据从第二个闭包返回到第一个闭包的正确方法是什么?还是我以完全错误的方式这样做? 问题是,我需要所有这些数据用于 tableview,因此需要一次所有数据。

【问题讨论】:

【参考方案1】:

一些想法:

    如果有可能在一个请求中将所有这些都返回给服务器,那可能会提供更好的性能。通常,与网络延迟相比,在服务器上执行请求所需的时间是无关紧要的。如果您可以避免需要发出一个请求、获得响应,然后再发出更多请求,那将是理想的。

    或者您可能提前请求一定距离内的位置,将其缓存,然后“显示附近位置的交易”可能不需要这两组请求。

    (我知道这两种方法都不适合您,但如果可以的话,还是要考虑一下。如果您可以消除连续请求并专注于大部分并发请求,您将获得更好的性能。)

    让我们暂时假设上述方法不是一个选项,并且您遇到了一个获取附近位置的请求和另一组获取交易的请求。然后你有几个选择:

    您绝对可以通过一次回调走上您正在考虑的道路。例如,您可以发出所有请求,在发起每个请求之前执行dispatch_group_enter,在每个请求完成时执行dispatch_group_leave,然后发出dispatch_group_notify,每个@987654325 都会调用该dispatch_group_notify @ 呼叫已被相应的 leave 呼叫抵消。因此,在每个请求完成时构建您的响应对象,并且只有在完成后才调用完成闭包。

    另一种方法是使用一个更像枚举闭包的闭包,在每个站点的交易进入时调用它。这样,UI 可以在事情进入时更新,而不是等待一切。如果您的网络速度较慢,则在数据进入时更新 UI 可能会更容易接受。 (例如,考虑 10 个请求,每个请求在慢速 3G 蜂窝连接上需要 1 秒完成:看着它们每秒弹出一个比 10 秒什么都看不到要容忍得多)。

    话虽如此,您可能希望完全放弃闭包。您可以考虑使用delegate-protocol 模式,在其中为您的请求指定delegate,然后为您从服务器获得的每个响应实现protocol 方法。这样你就可以在新的响应进来时更新 UI,而不是等到最后一个响应进来。但是我们认识到响应的类型非常不同(一个是网站列表,另一个是列表对于给定站点的交易,第三个是“我已经完成了”和/或“有一个错误”,所以当它开始变得复杂时,为这个接口定义一个协议可能会更好,并且就这样处理吧。

【讨论】:

谢谢罗! - 我真的很感谢你的回答。您对性能的看法是正确的,但是目前,我们需要这样做。我设法通过使用 dispatch_group 让它正常工作。关于您答案中的选项 2,哪一种方法性能最快? 后两者中的任何一个都会快得多(在每个请求完成时调用的块或委托协议模式),允许您在结果出现时更新 UI。因为您可能处理两种类型的请求(一种是“这里是站点列表”,另一种是“这里是特定站点的交易”)委托协议模式对我来说似乎更合乎逻辑,但你可以让两者都工作。在速度方面,允许请求同时运行(或更改 API 以一次调用返回所有内容)也将提高性能。 @Rob 感谢您对实施 global 调度组以实现我在此处概述的目标的想法:***.com/questions/41978206/…

以上是关于swift中具有多个闭包/ API请求的函数中的异步完成处理的主要内容,如果未能解决你的问题,请参考以下文章

Swift:具有符合协议的实例的完成闭包

➽06闭包

swift学习第十五天:闭包

如何处理完成闭包中的多个错误

将 Swift 闭包转换为 CFunctionPointer?

在 Swift 中传递和存储闭包/回调