所以这是我的班级
import Foundation
class APICall {
//The main Url for the api
var mainApiUrl = "http://url.de/api/"
func login(username: String, password: String) -> String {
let post = "user=\(username)&password=\(password)";
let action = "login.php";
let ret = getJSONForPOSTRequest(action: action, post: post)
return ret;
}
//Function to call a api and return the json output
func getJSONForPOSTRequest(action: String, post: String) -> String {
var ret: String?
let apiUrl = mainApiUrl + action;
let myUrl = URL(string: apiUrl);
var request = URLRequest(url:myUrl!);
request.httpMethod = "POST";
let postString = post;
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(error)")
return
}
print("response=\(response)")
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let login = parseJSON["Login"] as? String
print("login: \(login)")
ret = login
}
} catch {
print(error)
}
}
task.resume()
return ret!;
}
}
但是ret是零。在调试器中看到任务的内部稍后被另一个线程调用?
如果能解决这个问题?
谢谢你们
答案 0 :(得分:-1)
在另一个线程上调用数据任务完成闭包,并且在完成方法的执行之后,您需要稍微重新编写代码。不要为getJSONForPOSTRequest
提供String返回值,不要返回任何内容,而是有一个额外的参数,它是一个闭包,而是从dataTask闭包中调用它。
func getJSONForPOSTRequest(action: String, post: String, completion: (string: String) -> Void) {
// ...
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
// ... (Convert data to string etc.)
completion(string: myString)
}
task.resume()
}
请记住,这样做意味着一旦网络请求完成,就会调用完成处理程序,而不是立即调用。
修改强>
让我们从头开始。当您从iOS中的网络下载内容时,通常使用NSURLSession
。 NSURLSession
有许多方法可用于与网络交互的不同方法,但所有这些方法都使用不同的线程,通常是后台线程,它将独立于代码的其余部分工作。
考虑到这一点,当你调用dataTask
方法时,你会注意到你必须添加一个完成闭包作为参数之一(在你的例子中注意你正在使用一个叫做'尾随的东西closure'这是一个闭包,它是方法调用中的最后一个参数,不包含在其余参数的方法的括号内)。将闭包视为在不同时间执行的一段代码,它不会与其周围的其余代码一致执行(请参阅关于闭包here的Swift文档)。在这种情况下,一旦网络请求完成,就会调用闭包。网络请求不是即时的,因此我们通常使用后台线程来执行它们,同时向用户显示活动指示符等,并且仍然可以使用该应用程序。如果我们等待网络请求在与我们其余代码相同的线程上完成,那么它会导致应用程序出现滞后甚至冻结,这对用户来说非常糟糕。
回到你手边的例子;当您调用getJSONForPOSTRequest
方法时,该方法中的代码将在网络请求完成之前完成并返回,这就是我们不需要使用返回值的原因。网络请求完成后,您的关闭代码将被调用。因为后来调用了闭包,它也是从代码中完全不同的地方调用的,在这种情况下,它是从iOS的网络代码中调用的。因为如果你从闭包中返回一个值,你会尝试将值返回到你想要的网络代码,你想要将值返回给你自己的代码。
要将网络响应的值返回给您的代码,您需要自己定义一个闭包(或一个委托,但我不会在这里进行讨论)。如果您查看上面的示例代码,我已从getJSONForPOSTRequest
方法中删除了返回值,并添加了一个名为' completion'的新参数,如果您查看该参数的类型你可以看到它的(string: String) -> Void
,这定义了一个传递一个字符串的闭包(你将从网络下载的字符串)。现在我们在您的方法中有一个闭包,我们可以使用它来回调getJSONForPOSTRequest
的调用者以及我们从网络下载的数据。
让我们采用您的login
方法,看看我们如何在其中使用getJSONForPOSTRequest
:
func login(username: String, password: String, completion: (success: Bool) -> Void) {
let post = "user=\(username)&password=\(password)";
let action = "login.php";
let ret = getJSONForPOSTRequest(action: action, post: post) { string in
// This will be called once the network has responded and 'getJSONForPOSTRequest' has processed the data
print(string)
completion(success: true)
}
}
再次看到我们没有直接从登录方法返回任何内容,因为它必须依赖于调用网络的同步性。
现在可能会感觉到你开始进入一种名为“回调地狱”的东西,但这是处理网络的标准方法。在您的UI代码中,您将调用login
,这将是链的末尾。例如,这里有一些假设的UI代码:
func performLogin() {
self.activityIndicator.startAnimating()
self.apiCaller.login(username: "Joe", password: "abc123") { [weak self] success in
print(success)
// This will get called once the login request has completed. The login might have succeeded of failed, but here you can make the decision to show the user some indication of that
self?.activityIndicator.stopAnimating()
self?.loginCompleted()
}
}
希望澄清一些事情,如果你有任何其他问题,请问。