循环委托方法返回重复(多线程)

时间:2018-02-18 01:09:33

标签: ios swift multithreading

因此,我尝试创建一个应用程序,列出ISS计划通过用户位置的时间。一次打嗝就可以或多或少地顺利进行。

我的期望:UITableView中列出的大约5个不同的时间

我得到的:UITableView中列出的5次随机重复值。有时所有1个值,有时最后一个也是第二个和/或倒数第三个,有时2个值重复自己,任意数量的错误组合。一小部分测试正确返回。

最让我感到困惑的是,错误的结果是不一致的,所以我无法看到一种粗暴解决方案的方法。

相关代码: 首先是网络管理员班级/代表

import Foundation
import CoreLocation

//Delegate for thread communication
protocol NetworkManagerDelegate: class {
  func didGetPass(lastPass: Bool, pass: Pass)
  //Flag last model object to limit tableview reloads
}

//Using struct as manager class is not going to change state
struct NetworkManager {
  weak var delegate: NetworkManagerDelegate?
  //Set a base URL as far along as possible, will finish with data passed from function
  let baseURL = "http://api.open-notify.org/iss-pass.json?"

  func getPasses(coordinate: CLLocationCoordinate2D) {
    //complete URL
    let lat = coordinate.latitude
    let lon = coordinate.longitude
    let requestURL = URL(string: "\(baseURL)lat=\(lat)&lon=\(lon)")
    //begin task
    let task = URLSession.shared.dataTask(with: requestURL!) { (data, response, error) in
      if error != nil {
        print(error as Any)
        //Generic report on failure to connect
        print("Could not reach API")
      } else {
        do {
          //Get JSON from data to parse
          if let resultJSON = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String: Any] {
            //Get list of passes from JSON
            let passes = resultJSON["response"] as! [[String: Int]]
            //Set default parameters for delegate method
            var testPass: Pass
            var lastPass = false
            //loop through passes
            for pass in passes {
              //determine if last pass
              if pass == passes.last! {
                lastPass = true
              }
              testPass = Pass()/*This seems terribly inefficient to me
               However, attempting to create the object outside the loop and simply modify it
               leads to the same exact object being passed multiple times to the main thread, so
               the tableview's cells are all identical.*/
              testPass.durationInSeconds = pass["duration"] ?? 0
              //Convert date to Date object and set to testPass
              testPass.riseTime = Date(timeIntervalSince1970: (Double(pass["risetime"] ?? 0)))
              //Inform main thread via delegate
              DispatchQueue.main.async {
                self.delegate?.didGetPass(lastPass: lastPass, pass: testPass)
              }
            }
          }
        } catch {
          print(error.localizedDescription)
        }
      }
    }
    task.resume()
  }

}

在主线上:

extension TrackingController: UITableViewDataSource {
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return passes.count
  }

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    //Get cell, pass default if nil
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "passCell") as? PassCell else {
      return PassCell()
    }
    //Retrieve data from passes, assign to labels
    let pass = passes[indexPath.row]
    cell.timeLabel.text = "\(dateFormatter.string(from: pass.riseTime))"
    cell.durationLabel.text = "Duration: \(pass.durationInSeconds) Seconds"
    return cell
  }

}
extension TrackingController: NetworkManagerDelegate {
  func didGetPass(lastPass: Bool, pass: Pass) {
    passes.append(pass)
    if lastPass { //reload only once api calls are done
      passList.reloadData()
    }
  }
}

这应该是从调用网络管理器的一个功能时开始触发的所有代码。任何人都可以向我解释这个吗?我手动检查了API,我不应该在这里收到重复的输出。

编辑:我只是尝试将DispatchQueue.main.async更改为DispatchQueue.main.sync,虽然它似乎解决了问题,但我对多线程的理解让我相信这无法在另一个线程上运行调用。也就是说,我对多线程的理解并不实用,所以我可以对此进行更正或更好地解决我的问题。

3 个答案:

答案 0 :(得分:1)

理想情况下,在主线程上,在收到最后一个对象之前,您不会对数据执行任何操作。因此,一次性通过整个阵列是一种更好的方法。

您可以这样做:

func didGetPasses(_ passes: Passes) { 
    passes.append(passes); 
    passList.reloadData(); 
} 

并致电

self.delegate?.didGetPasses(passes)

for循环完成后。

PS:您还应该考虑使用闭包。它有助于处理逻辑并使代码在呼叫站点更“就地”。

答案 1 :(得分:0)

guard let cell = tableView.dequeueReusableCell(withIdentifier: "passCell") as? PassCell else {
  return PassCell()
}

正确?好像你正在那里返回一个未初始化的PassCell

答案 2 :(得分:0)

我决定根据Kunal Shah的建议启发答案。我停止了循环委托调用,而是将一组Pass对象放在一起发送到主线程。

我不想这样做,因为我之前的经历导致由于某种原因发送空集合,但它似乎在这里工作。这样我仍然只重新加载一次tableview,但也只调用一次委托方法。