无法弄清楚我的数据在网络 Swift 中传递到哪里



我是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时,尚未收到结果。

当您启动一堆异步任务并想知道它们何时完成时,一种常见的模式是使用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()
}
}

最新更新