【问题标题】:Couldn't figure out where my data was passed in networking Swift无法弄清楚我的数据在网络 Swift 中的传递位置
【发布时间】:2020-12-18 16:18:39
【问题描述】:

我是 Swift 新手,通常缺乏编程经验。目前我正在开发一个项目,试图在视图控制器上显示星球大战角色列表,但我在通过网络管理器传递数据时遇到了一些问题。当我运行程序时,我无法在屏幕上显示名称标签。 我检查了 tableView 单元格,标签与viewController 连接。我觉得这个问题与网络管理员有关,但我自己无法弄清楚。

var charactersArray: [Characters] = []

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    getCharacters(){
        DispatchQueue.main.async {
            self.starWarTableViewController.reloadData()
        }
    }
    self.title = "Star War Characters"
    // print(charactersArray), returns an empty array
}

private func getURL() -> String {
    return "https://swapi.dev/api/people/"
}

func getCharacters(completion: @escaping () -> Void) {
    self.starWarTableViewController.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "TableViewCell")
    self.starWarTableViewController.dataSource = self
    self.starWarTableViewController.delegate = self
    DispatchQueue.global(qos: .background).async {
        for i in 1...20 {
            NetworkingManager.shared.getDecodedObject(from: self.getURL() + "\(i)"){
                  (characters: Characters?, error) in
                  guard let characters = characters else{ return }
                  self.charactersArray.append(characters)
                  print(self.charactersArray) //this will return an array list of characters names
            }
        }
        print(self.charactersArray) // here the characterArray is empty
    }
    completion()
}

根据我的发现,问题出现在我的网络管理器或表格视图单元格中

enum NetworkError: Error {
    case invalidURLString
}

final class NetworkingManager{
    
    static let shared = NetworkingManager()
    
    private init(){
        
    }
    
    func getDecodedObject <T: Decodable> (from urlString: String, completion: @escaping (T?, Error?) -> Void){
        guard let url = URL(string: urlString) else {
            completion(nil, NetworkError.invalidURLString)
            return
        }
        URLSession.shared.dataTask(with: url){
            (data, response, error) in
            guard let data = data else{
                completion(nil, error)
                return }
            guard let characters = try? JSONDecoder().decode(T.self, from: data) else{ return }
            completion(characters, nil)
        }.resume()
    }   
}

class TableViewCell: UITableViewCell {

    @IBOutlet weak var nameLabel: UILabel!
    
    func configure (with characters: Characters) {
        self.nameLabel.text = characters.name
    }
}

这里是字符

struct Characters: Decodable {
    let name: String
    
    enum CodingKeys: String, CodingKey {
        case name
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
//        print(name), this will return a list of character names
    }
}

这是表格视图扩展

extension ViewController: UITableViewDataSource, UITableViewDelegate{
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.charactersArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
        cell.configure(with: self.charactersArray[indexPath.row])
//        cell.nameLabel.text = "Hi"
        return cell
    }
    
}

【问题讨论】:

  • 你必须阅读异步编程。简而言之,您现在调用completion() 时,尚未检索到数据(只会在稍后的某个时间点检索到 - 即异步)。此外,因为您要从多个 URL 获取数据,所以您需要“等待”直到它们都被检索到(阅读 DispatchGroup.notify),然后才调用 completion()
  • 与手头的问题无关,我会将Characters 重命名为单数(即没有尾随s),因为它显然代表单个命名字符,而不是它们的集合。我会避免使用Character,因为它与同名的标准类型冲突。我不知道这些名字代表了什么样的“字符”,所以我无法给出具体的建议,只是对模型类型命名的一些想法......
  • 下面我已经回答了有关如何执行 20 次查询、按排序顺序收集结果以及在完成后更新模型和 UI 的问题。话虽如此,正确的解决方案通常是在您的服务器中写入和端点以在单个响应中返回 20 个值。我们在这里做的事情效率很低。

标签: ios swift networking decode


【解决方案1】:

现在,您正在启动一系列异步网络请求,但在请求启动后调用完成处理程序,而不是等待它们何时完成。因此,当您调用completion 时,还没有收到结果。

当你启动一堆异步任务并想知道它们何时完成时,一个常见的模式是使用DispatchGroup,在异步调用之前调用enter,在完成处理程序中调用leave,然后然后在提供给notify 的闭包中指定当它们全部完成时你想要做什么。如果您在 notify 中调用您自己的完成处理程序,那么在请求完成并且您有数据可以在 UI 中显示之前,它不会被调用。

其他一些观察:

  1. getDecodedObject 已经是异步方法,因此无需将其分派到后台队列。

  2. 您需要从主队列更新模型和 UI。 (URLSession 在串行后台队列上调用其完成处理程序。)要完成此操作,基本上可以告诉上述notify.main 队列上运行其闭包。在您准备好更新 UI 之前,我也不会更新模型对象。

  3. 当并行执行一系列异步网络请求时,您无法保证它们可能完成的顺序。因此,您通常希望使用一些独立于任务完成顺序的结构,例如字典。然后,当它们完成后,如果你想构建一个排序的结果数组,然后构建结果数组。

因此:

func getCharacters(completion: @escaping ([Characters]) -> Void) {
    // to keep track of when the 20 requests finish

    let group = DispatchGroup()

    // temporary structure to keep track of the results

    var charactersDictionary: [Int: Characters] = [:]

    // perform the requests

    for i in 1...20 {
        group.enter()
        NetworkingManager.shared.getDecodedObject(from: getURL() + "\(i)") { (characters: Characters?, error) in
            defer { group.leave() }
            guard let characters = characters else { return }
            charactersDictionary[i] = characters
        }
    }

    // when the group tells us that all 20 are done, let's build array of the
    // results and pass it back to the main queue via the completion handler parameter.

    group.notify(queue: .main) {
        let results = (1...20).compactMap { charactersDictionary[$0] }
        print(results)
        completion(results)
    }
}

注意,顺便说一下,我已经从这个例程中提取了表格配置代码(因为你真的不想将网络代码与 UI 代码混合):

func configureTableView() {
    starWarTableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "TableViewCell")
    starWarTableView.dataSource = self
    starWarTableView.delegate = self
}

我还将该插座重命名为starWarTableView,因为它不是视图控制器,而是表视图:

不管怎样,你可以这样称呼它:

override func viewDidLoad() {
    super.viewDidLoad()

    configureTableView()

    getCharacters { characters in
        self.charactersArray = characters
        self.starWarTableView.reloadData()
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-12
    相关资源
    最近更新 更多