【问题标题】:API Threading Issue iOSAPI 线程问题 iOS
【发布时间】:2017-11-13 01:15:21
【问题描述】:

所以我在运行 API 调用时遇到问题,该调用的响应包含另一个 API 调用。

这是第一个函数:

class APICaller{

    weak var delegate:APIDelegate?

    func getCharacter(x:Int){
        let character = CharacterModel()
        let url = URL(string: "https://swapi.co/api/people/\(x)/")
        let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
            if error != nil{
                print("Error downloading character information. Empty character returned.")
            } else {
                if let content = data {

                    do{
                        let charJSON = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String: Any]
                        character.name = (charJSON?["name"] as? String)?.description ?? ""
                        character.height = Int((charJSON?["height"] as? String)?.description ?? "0") ?? 0
                        character.mass = Int((charJSON?["mass"] as? String)?.description ?? "0") ?? 0
                        character.hairColor = (charJSON?["hair_color"] as? String)?.description ?? ""
                        character.skinColor = (charJSON?["skin_color"] as? String)?.description ?? ""
                        character.eyeColor = (charJSON?["eye_color"] as? String)?.description ?? ""
                        character.birthYear = (charJSON?["birth_year"] as? String)?.description ?? ""
                        character.gender = (charJSON?["gender"] as? String)?.description ?? ""
                        character.homeWorld = self.getPlanet(uri: (charJSON?["homeworld"] as? String)?.description ?? "")
//The homeward part of the response is another URL and as such requires another API Call to get anything meaningful
                            DispatchQueue.main.async {
                                self.delegate?.didGetStarWarsCharacter(characterData:character)
                            }
                        }catch{
                            print("Error downloading character information. Empty or incomplete character returned")
                        }
                    }
                }
            }
            task.resume()
        }
    private func getPlanet(uri:String)->String{
        if uri == ""{
            return uri // return empty string if the original call doesn't get anything.
        }
        var result = ""
        let url = URL(string:uri)
        let task = URLSession.shared.dataTask(with: url!){(data,response,error)->Void in
            if error != nil{
                result = "No Planet Found"
            }else{
                if let planet = data{
                    do{
                        let planetJSON = try JSONSerialization.jsonObject(with: planet, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String:Any]
                        //print(planetJSON?["name"] as? String ?? "No Planet")
                        result = (planetJSON?["name"] as? String)?.description ?? "No Planet Found"
                    }catch{
                        result = "No Planet Found"
                    }
                }
            }
        }// end of task, result is lost due to multithreading
        task.resume()
        return result
        }
    }

所以我知道为getPlanet 运行任务发生在另一个线程上,并且此方法在任务完成运行之前返回。因此,当委托获取 CharacterModel 时,其 homeWorld 参数为空。

例如,如果我在 getPlanet 函数运行后调用 print(character.homeWorld),我会收到一个空字符串。

我想不出这个问题的一个很好的解决方案。

【问题讨论】:

  • 将escaping closure 添加到您的getPlanet(uri:String) 方法以将结果返回给调用者。当您从服务器成功接收数据时调用该闭包。 stackoverflow.com/a/45976392/5912335
  • 所以你是说在任务中我应该有一个注解为 @escaping 的闭包,它将结果发送回类 API Caller 的对象?
  • 没错。并从方法定义中删除您的 getPlanet 方法的返回值 (-> String)。这不是必需的。
  • 好的。所以删除返回,可能设置一个快速的 String 实例变量,并在从调用中获取 String 后,将其发送到转义闭包中的该实例变量。之后剩下的就是如何确保在调用委托的 didGetStarWarsCharacter 之前将该值添加到角色的 homeWorld 参数中。
  • 这很简单。当调用getPlanet 方法时,您将声明与之关联的闭包。因此,当您在服务响应后调用 escaping closure 时,将执行关联的闭包并传递结果字符串。您可以使用它来设置homeWorld 属性。这个答案是您了解如何在服务调用后使用闭包返回数据所需的全部内容:stackoverflow.com/a/39504347/5912335

标签: ios multithreading


【解决方案1】:

getPlanet 正在执行异步调用。 result 实例不会保存接收到的数据。取而代之的是在 getPlanet 中使用完成块,并在接收数据时调用此完成块。像这样的东西。请继续阅读closures。

class APICaller{

    weak var delegate:APIDelegate?

    func getCharacter(x:Int){
        let character = CharacterModel()
        let url = URL(string: "https://swapi.co/api/people/\(x)/")
        let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
            if error != nil{
                print("Error downloading character information. Empty character returned.")
            } else {
                if let content = data {

                    do{
                        let charJSON = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String: Any]
                        character.name = (charJSON?["name"] as? String)?.description ?? ""
                        character.height = Int((charJSON?["height"] as? String)?.description ?? "0") ?? 0
                        character.mass = Int((charJSON?["mass"] as? String)?.description ?? "0") ?? 0
                        character.hairColor = (charJSON?["hair_color"] as? String)?.description ?? ""
                        character.skinColor = (charJSON?["skin_color"] as? String)?.description ?? ""
                        character.eyeColor = (charJSON?["eye_color"] as? String)?.description ?? ""
                        character.birthYear = (charJSON?["birth_year"] as? String)?.description ?? ""
                        character.gender = (charJSON?["gender"] as? String)?.description ?? ""
                        self.getPlanet(uri: (charJSON?["homeworld"] as? String)?.description ?? "", completion:
                            { (result:String) in
                                character.homeWorld = result
                                DispatchQueue.main.async {
                                    self.delegate?.didGetStarWarsCharacter(characterData:character)
                                }
                        }
                        )
                        //The homeward part of the response is another URL and as such requires another API Call to get anything meaningful

                    }catch{
                        print("Error downloading character information. Empty or incomplete character returned")
                    }
                }
            }
        }
        task.resume()
    }
    private func getPlanet(uri:String, completion:@escaping (_ response:String)->Void){
        if uri == ""{
            completion(uri) // return empty string if the original call doesn't get anything.
        }
        var result = ""
        let url = URL(string:uri)
        let task = URLSession.shared.dataTask(with: url!){(data,response,error)->Void in
            if error != nil{
                result = "No Planet Found"
            }else{
                if let planet = data{
                    do{
                        let planetJSON = try JSONSerialization.jsonObject(with: planet, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String:Any]
                        //print(planetJSON?["name"] as? String ?? "No Planet")
                        result = (planetJSON?["name"] as? String)?.description ?? "No Planet Found"
                    }catch{
                        result = "No Planet Found"
                    }
                    completion(result)
                }
            }
        }// end of task, result is lost due to multithreading
        task.resume()
    }
}

【讨论】:

  • 谢谢。因此,如果我的理解是正确的,我会传递一个名为“completion”的闭包,该闭包将 String 参数作为函数的参数。 @escaping 注释允许闭包转义方法并从 getCharacter 方法中访问参数。然后在我的 getPlanet 函数中,我确保在需要访问结果的任何时候调用闭包。太棒了。
  • 是的。欢迎使用 Swift!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-04
  • 1970-01-01
  • 2011-02-15
相关资源
最近更新 更多