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



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

我读过很多类似的问题,例如,如何在 Swift 中获取要从 NSURLSessionDataTask 返回的数据以及如何在 Swift 中使用 completionHandler Closure 和 return?。这些帮助了我,但我的问题有点不同。

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

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

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

问题是我不知道如何完成处理一个函数中的多个闭包,在本例中为两个闭包。

我在下面发布了我的代码 - 我知道它很长,也许不是那么干净。但是,关键是当 im 更新报价到我的 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)
    }

这是主节点在调用函数时将打印的内容,其中 offer 为空。

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

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

几个想法:

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

    或者,也许您提前请求了一定距离内的位置,将其缓存,然后"显示附近位置的优惠"可能不需要这两组请求。

    (我知道这些都不适合你,但如果可以的话,这是需要考虑的事情。如果您可以消除连续请求并专注于大部分并发请求,那么您将获得更好的性能。

  2. 让我们假设上述方法不是一种选择,并且您只能使用一个请求来获取附近的位置,而另一个请求则需要获得优惠。然后,您有几个选择:

    • 绝对可以通过一次回调走上您正在考虑的道路。例如,您可以发出所有请求,在发起每个请求之前执行dispatch_group_enter,在每个请求完成后执行dispatch_group_leave,然后发出一个dispatch_group_notify,当每个enter调用被相应的leave调用偏移时将调用该。因此,在每个请求完成时构建响应对象,并且仅在请求完成后调用完成闭包。

    • 另一种方法是使用一个行为更像枚举闭包的闭包,当每个站点的交易进入时调用该闭包。这样,UI 就可以随着内容的出现而更新,而不是等待所有内容。如果你使用的是慢速网络,则在数据传入时更新 UI 可能更容易容忍。(例如,考虑十个请求,每个请求在慢速 3G 蜂窝连接上需要 1 秒完成:看着它们每秒弹出一个比在 10 秒内什么也看不到要容易忍受得多(。

    • 话虽如此,您可能希望完全放弃关闭。您可以考虑delegate - protocol模式,在该模式下,您可以为请求指定一个delegate,然后为从服务器获得的每个响应实现protocol方法。这样,您就可以在新响应进入时更新 UI,而不是将所有内容保留到最后一个响应。但是我们认识到有非常不同类型的响应(一个是站点列表,另一个是给定站点的列表交易,第三个是"我都完成了"和/或"有一个错误"(,所以当它开始变得如此复杂时,最好为此接口定义一个协议, 并以这种方式处理它。

最新更新