【问题标题】:Swift: Asynchronous dataTask never terminatesSwift:异步 dataTask 永远不会终止
【发布时间】:2018-09-03 19:18:36
【问题描述】:

我有一个函数可以抓取嵌入网站的 JSON 文件并返回一个字符串数组。因为它返回的数据对我的视图控制器来说是必不可少的,所以我需要在视图加载之前运行这个函数。它在视图控制器的初始化中被调用。

func scrapeBuses() -> [String] {
    let config = URLSessionConfiguration.default
    //config.waitsForConnectivity = true
    let defaultSession = URLSession(configuration: config)
    let url = URL(string: link to a JSON file)
    let request = NSMutableURLRequest(url: url!)
    request.cachePolicy = .reloadIgnoringLocalCacheData
    var loops = [String]()
    let group = DispatchGroup()
    group.enter()
    DispatchQueue.main.async {
        let task = defaultSession.dataTask(with: request as URLRequest) { data, response, error in
            do {
                print("Getting information from website")
                if let error = error {
                    print(error.localizedDescription)
                } else if let data = data, let response = response as? HTTPURLResponse, response.statusCode == 200 {
                    print("in async")
                    loops.append("test2")
                }
            }
            catch { print(error)}
        }
    }
    task.resume()
    print("about to return")
    //return loops
    }
    group.wait()
    return loops
}

因为它是异步的,所以我添加了 DispatchGroupwait() 语句,以便我的主线程等到这些基本数据被抓取后再继续线程的其余部分。然而,我注意到,当这个视图控制器的 segue 发生时(即,当视图控制器被初始化时)没有任何其他内容被打印并且模拟停止。显然这意味着主线程正在等待scrape() 完成,但是为什么它会无限运行呢?当我在我的网络浏览器和 Rested 中访问该网站时,我可以看到它正确地托管了 JSON。

编辑:

我尝试了使用完成处理程序而不是DispatchQueue.wait() 的同一函数的另一个版本。下面是代码:

func scrapeBuses(completion: @escaping ([String]) -> Void) {
    let config = URLSessionConfiguration.default
    let defaultSession = URLSession(configuration: config)
    let url = URL(string: "https://www.cmunc.net/assets/appData.json")
    let request = NSMutableURLRequest(url: url!)
    request.cachePolicy = .reloadIgnoringLocalCacheData
    var loops = [String]()

        let task = defaultSession.dataTask(with: request as URLRequest) { data, response, error in
            print("Getting information from website")
            if let error = error {
                print(error.localizedDescription)
            } else if let data = data, let response = response as? HTTPURLResponse, response.statusCode == 200 {
                print("in async")
                loops.append("test2")
            }
            completion(loops)
        }
        task.resume()
}

这是视图控制器的init调用的函数:

func refresh() {
    var newArray = [String]()
    scrapeBuses { loops in
        newArray = loops
        print("calling scrapeBuses closure")
    }
    print(newArray)
}

使用完成处理程序,代码可以正确执行。 scrapeBuses 中的打印语句和 closeure 中的打印语句。非常感谢所有提供见解的人。

【问题讨论】:

  • 我尝试了一个完成处理程序,没有任何改变。请在你的问题中显示你尝试了什么。您很可能在滥用完成处理程序。无论如何永远不要在主线程中等待。
  • 请发布有效代码。你有不匹配的花括号。 do/catch 是不必要的,因为 do 块内没有任何东西抛出。
  • 请尝试了解异步数据处理的工作原理。您必须将print(newArray)(或处理数组的真实代码)放入闭包中,因为闭包在refresh完成后稍后执行。跨度>
  • 正如我所猜测的,您在滥用完成处理程序模式。我猜你知道你通常在viewDidLoad() 方法中设置视图。因为 iOS 在所有视图都准备好设置时调用该方法。同样。您需要在完成处理程序中编写您使用loops 的所有内容。因为当您的 loops 准备好使用时,您更新的代码会调用该方法。
  • 您需要从完成处理程序代码中删除DispatchQueue.main.async { }

标签: ios swift asynchronous nsurlsession


【解决方案1】:

您应该向您的 scrapeBuses 函数添加一个完成处理程序。

func scrapeBuses(completion: ([String]?, Error?) -> Void) {
    // etc (remove all mentions of groups)

    let task = defaultSession.dataTask(with: request as URLRequest) { data, response, error in
        do {
            print("Getting information from website")
            if let error = error {
                DispatchQueue.main.async { // Call back on the main queue
                    completion(nil, error)
                }
            } else if let data = data, let response = response as? HTTPURLResponse, response.statusCode == 200 {
                print("in async")
                loops.append("test2")
            }
            DispatchQueue.main.async {
                completion(loops, nil)
            }
        } 
        catch { 
            DispatchQueue.main.async {
                completion(nil, error)
            }
        }
    }
    task.resume()
}

那么……

func viewDidLoad() {
    super.viewDidLoad() 

    scrapeBuses { loops, error in
        if let error = error {    
            print(error)
            return
        }
        // Do something with loops
    }
}

【讨论】:

  • 我在问题中添加了具有这种完成处理程序样式的版本;使用你的方法,主队列不再停止,但函数闭包和 dataTask 块中的代码都不会被执行。
  • 你试过调试吗?添加断点并单步执行代码?
【解决方案2】:

因为你错过了

loops.append("test2")
group.leave()

或者在回调之上更好

defer { group.leave() }

顺便说一句,最好有一个活动指示器,而不是wait

//

试试这个

func scrapeBuses(completion:@escaping(_ res:[String]?) -> Void ) {
    let config = URLSessionConfiguration.default
    //config.waitsForConnectivity = true
    let defaultSession = URLSession(configuration: config)
    let url = URL(string: link to a JSON file)
    let request = NSMutableURLRequest(url: url!)
    request.cachePolicy = .reloadIgnoringLocalCacheData
    var loops = [String]()
    let task = defaultSession.dataTask(with: request as URLRequest) { data, response, error in
            do {
                print("Getting information from website")
                if let error = error {
                    print(error.localizedDescription)
                     completion(nil)
                } else if let data = data, let response = response as? HTTPURLResponse, response.statusCode == 200 {
                    print("in async")
                    loops.append("test2")
                    completion(loops)
                }
            }
            catch { print(error)
                     completion(nil)
                  }
        }
    }
    task.resume()

}

【讨论】:

  • 在 append 语句之后添加 group.leave() 并没有改变任何内容。 defer 声明会去哪里?在DispatchQueue.main.async 块之后?
  • 顺便说一句,当它们全部结束时,您应该将 group 用于您想要的许多异步任务
  • @Sh_Khan 您的答案的第一部分不正确。调用leave(如果使用)需要在完成块的末尾调用,无论是否有错误。
  • 这就是为什么我说 defer 在任何情况下都最好被调用
猜你喜欢
  • 1970-01-01
  • 2014-05-13
  • 2021-10-08
  • 2014-05-29
  • 1970-01-01
  • 2017-07-13
  • 1970-01-01
  • 1970-01-01
  • 2015-04-21
相关资源
最近更新 更多