【问题标题】:Swift 4: How to asynchronously use URLSessionDataTask but have the requests be in a timed queue?Swift 4:如何异步使用 URLSessionDataTask 但请求在定时队列中?
【发布时间】:2017-12-07 11:58:19
【问题描述】:

基本上我有一些 JSON 数据,我希望从一堆 URL(都来自同一个主机)中检索,但是我只能大约每 2 秒请求一次这些数据,并且一次只能请求一个,否则我会被服务器“时间禁止”。正如您将在下面看到的;虽然 URLSession 非常快,但当我有大约 700 个网址要通过时,它也几乎立即让我被禁止。

我将如何在 URLSession 中创建一个队列(如果它的功能支持它)并让它与我的主线程异步工作;它是否在自己的线程上连续工作,并且仅在完成上一个请求后 2 秒后才尝试队列中的每个项目?

for url in urls {
    get(url: url)
}


func get(url: URL) {
    let session = URLSession.shared
    let task = session.dataTask(with: url, completionHandler: { (data, response, error) in

        if let error = error {
            DispatchQueue.main.async {
                print(error.localizedDescription)
            }
            return
        }
        let data = data!

        guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
            DispatchQueue.main.async {
                print("Server Error")
            }
            return
        }
        if response.mimeType == "application/json" {
            do {
                let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
                if json["success"] as! Bool == true {
                    if let count = json["total_count"] as? Int {
                        DispatchQueue.main.async {
                            self.itemsCount.append(count)
                        }
                    }
                }
            } catch {
                print(error.localizedDescription)
            }
        }
    })
    task.resume()
}

【问题讨论】:

    标签: swift multithreading asynchronous networking swift4


    【解决方案1】:

    递归解决了这个问题

    import Foundation
    import PlaygroundSupport
    
    // Let asynchronous code run
    PlaygroundPage.current.needsIndefiniteExecution = true
    
    func fetch(urls: [URL]) {
    
        guard urls.count > 0 else {
            print("Queue finished")
            return
        }
    
        var pendingURLs = urls
        let currentUrl = pendingURLs.removeFirst()
    
        print("\(pendingURLs.count)")
    
        let session = URLSession.shared
        let task = session.dataTask(with: currentUrl, completionHandler: { (data, response, error) in
            print("task completed")
            if let _ = error {
                print("error received")
                DispatchQueue.main.async {
                    fetch(urls: pendingURLs)
                }
                return
            }
    
            guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
                print("server error received")
                DispatchQueue.main.async {
                    fetch(urls: pendingURLs)
                }
                return
            }
            if response.mimeType == "application/json" {
                print("json data parsed")
                DispatchQueue.main.async {
                    fetch(urls: pendingURLs)
                }
            }else {
                print("unknown data")
                DispatchQueue.main.async {
                    fetch(urls: pendingURLs)
                }
            }
        })
    
        //start execution after two seconds
        Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (timer) in
            print("resume called")
            task.resume()
        }
    }
    
    var urls = [URL]()
    for _ in 0..<100 {
        if let url = URL(string: "https://google.com") {
            urls.append(url)
        }
    }
    
    fetch(urls:urls)
    

    【讨论】:

    • 成功使用了这个答案。堆栈可以处理的深度是否有实际限制?
    • 由于我们使用异步来生成新任务,因此它不会充当堆栈。因此,您可以随意使用数组可以处理的任意数量。
    【解决方案2】:

    最简单的方法是递归调用:

    1. 想象一下,你的 url 有一个数组。

    2. 在最初执行 for 循环的地方,将其替换为单个调用 get(url:)self.get(urls[0])

    3. 然后在 self.itemsCount.append(count) 之后的响应关闭的 and 处添加这一行:

      self.urls.removeFirst()
      Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { (_) in
      self.get(url: urls[0])
      }
      

    【讨论】:

    • 几个小时后,Ankit Kumar Gupta 似乎带着你的想法跑了。尽管您的答案没有充实细节,但它已被拾取并且很有价值。 +1
    【解决方案3】:

    使DispatchQueue 在线程上运行您的代码。您不需要在主线程上执行此工作。所以,

    // make serial queue
    let queue = DispatchQueue(label: "getData")
    
    // for delay
    func wait(seconds: Double, completion: @escaping () -> Void) {
        queue.asyncAfter(deadline: .now() + seconds) { completion() }
    }
    
    // usage
    for url in urls {
      wait(seconds: 2.0) {
          self.get(url: url) { (itemCount) in
             // update UI related to itemCount
          }
      }
    }
    

    顺便说一句,你的get(url: url) 功能不是很好。

    func get(url: URL, completionHandler: @escaping ([Int]) -> Void) {
        let session = URLSession.shared
        let task = session.dataTask(with: url, completionHandler: { (data, response, error) in
    
            if let error = error {
                print(error.localizedDescription)
                /* Don't need to use main thread
                    DispatchQueue.main.async {
                        print(error.localizedDescription)
                    }
                */
                return
            }
            let data = data!
    
            guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
                print("Server Error")
                /* Don't need to use main thread
                    DispatchQueue.main.async {
                        print("Server Error")
                    }
                */
                return
            }
            if response.mimeType == "application/json" {
                do {
                    let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
                    if json["success"] as! Bool == true {
                        if let count = json["total_count"] as? Int {
    
                            self.itemsCount.append(count)
    
                            // append all data that you need and pass it to completion closure
                            DispatchQueue.main.async {
                                completionHandler(self.itemsCount)
                            }
                        }
                    }
                } catch {
                    print(error.localizedDescription)
                }
            }
        })
        task.resume()
    }
    

    我建议你学习GCD(用于线程)和escaping closure(用于完成处理程序)的概念。

    【讨论】:

    • 如果连接速度很慢并且调用服务器之间的实际时间可能会有所不同怎么办,因为get 会立即返回。
    • @DenisLitvin 在这种情况下,一些请求将失败并打印错误消息。如果你想处理这个问题,你需要从 urlsession 数据任务中设置超时设置。
    • 不,由于服务器限制,该 IP 将被禁止。如问题所述
    • 这不会在每个请求之间等待 2 秒,因为 get 异步运行(即立即返回),因此实际上它将在到达 for 循环后两秒启动所有请求。
    猜你喜欢
    • 2019-11-05
    • 2014-10-06
    • 2018-12-11
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 2020-05-05
    • 2014-04-07
    • 2019-07-18
    相关资源
    最近更新 更多