ios 从 Swift 函数中的异步调用返回数据
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25203556/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Returning data from async call in Swift function
提问by Mark Tyers
I have created a utility class in my Swift project that handles all the REST requests and responses. I have built a simple REST API so I can test my code. I have created a class method that needs to return an NSArray but because the API call is async I need to return from the method inside the async call. The problem is the async returns void. If I were doing this in Node I would use JS promises but I can't figure out a solution that works in Swift.
我在 Swift 项目中创建了一个实用程序类,用于处理所有 REST 请求和响应。我已经构建了一个简单的 REST API,所以我可以测试我的代码。我创建了一个需要返回 NSArray 的类方法,但是因为 API 调用是异步的,所以我需要从异步调用中的方法返回。问题是异步返回无效。如果我在 Node 中这样做,我会使用 JS 承诺,但我无法找出适用于 Swift 的解决方案。
import Foundation
class Bookshop {
class func getGenres() -> NSArray {
println("Hello inside getGenres")
let urlPath = "http://creative.coventry.ac.uk/~bookshop/v1.1/index.php/genre/list"
println(urlPath)
let url: NSURL = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
var resultsArray:NSArray!
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
println("Task completed")
if(error) {
println(error.localizedDescription)
}
var err: NSError?
var options:NSJSONReadingOptions = NSJSONReadingOptions.MutableContainers
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: options, error: &err) as NSDictionary
if(err != nil) {
println("JSON Error \(err!.localizedDescription)")
}
//NSLog("jsonResults %@", jsonResult)
let results: NSArray = jsonResult["genres"] as NSArray
NSLog("jsonResults %@", results)
resultsArray = results
return resultsArray // error [anyObject] is not a subType of 'Void'
})
task.resume()
//return "Hello World!"
// I want to return the NSArray...
}
}
回答by Alexey Globchastyy
You can pass callback, and call callback inside async call
您可以传递回调,并在异步调用中调用回调
something like:
就像是:
class func getGenres(completionHandler: (genres: NSArray) -> ()) {
...
let task = session.dataTaskWithURL(url) {
data, response, error in
...
resultsArray = results
completionHandler(genres: resultsArray)
}
...
task.resume()
}
and then call this method:
然后调用这个方法:
override func viewDidLoad() {
Bookshop.getGenres {
genres in
println("View Controller: \(genres)")
}
}
回答by Rob Napier
Swiftz already offers Future, which is the basic building block of a Promise. A Future is a Promise that cannot fail (all terms here are based on the Scala interpretation, where a Promise is a Monad).
Swiftz 已经提供了 Future,它是 Promise 的基本构建块。Future 是一个不会失败的 Promise(这里的所有术语都基于 Scala 解释,其中 Promise 是 Monad)。
https://github.com/maxpow4h/swiftz/blob/master/swiftz/Future.swift
https://github.com/maxpow4h/swiftz/blob/master/swiftz/Future.swift
Hopefully will expand to a full Scala-style Promise eventually (I may write it myself at some point; I'm sure other PRs would be welcome; it's not that difficult with Future already in place).
希望最终会扩展到一个完整的 Scala 风格的 Promise(我可能会在某个时候自己编写它;我相信其他 PR 会受到欢迎;Future 已经到位,这并不难)。
In your particular case, I would probably create a Result<[Book]>
(based on Alexandros Salazar's version of Result
). Then your method signature would be:
在您的特定情况下,我可能会创建一个Result<[Book]>
(基于Alexandros Salazar 的版本Result
)。那么你的方法签名将是:
class func fetchGenres() -> Future<Result<[Book]>> {
Notes
笔记
- I do not recommend prefixing functions with
get
in Swift. It will break certain kinds of interoperability with ObjC. - I recommend parsing all the way down to a
Book
object before returning your results as aFuture
. There are several ways this system can fail, and it's much more convenient if you check for all of those things before wrapping them up into aFuture
. Getting to[Book]
is much better for the rest of your Swift code than handing around anNSArray
.
- 我不建议
get
在 Swift 中为函数添加前缀。它会破坏与 ObjC 的某些类型的互操作性。 - 我建议
Book
在将结果作为Future
. 这个系统有几种可能失败的方式,如果你在将它们打包成Future
.[Book]
对于 Swift 代码的其余部分来说,获取比传递NSArray
.
回答by Jaydeep
Swift 4.0
斯威夫特 4.0
For async Request-Response you can use completion handler. See below I have modified the solution with completion handle paradigm.
对于异步请求-响应,您可以使用完成处理程序。请参阅下文,我已使用完成句柄范例修改了解决方案。
func getGenres(_ completion: @escaping (NSArray) -> ()) {
let urlPath = "http://creative.coventry.ac.uk/~bookshop/v1.1/index.php/genre/list"
print(urlPath)
guard let url = URL(string: urlPath) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary {
let results = jsonResult["genres"] as! NSArray
print(results)
completion(results)
}
} catch {
//Catch Error here...
}
}
task.resume()
}
You can call this function as below:
您可以按如下方式调用此函数:
getGenres { (array) in
// Do operation with array
}
回答by Rob
The basic pattern is to use completion handlers closure.
基本模式是使用完成处理程序关闭。
For example, in the forthcoming Swift 5, you'd use Result
:
例如,在即将发布的 Swift 5 中,您将使用Result
:
func fetchGenres(completion: @escaping (Result<[Genre], Error>) -> Void) {
...
URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error {
DispatchQueue.main.async {
completion(.failure(error))
}
return
}
// parse response here
let results = ...
DispatchQueue.main.async {
completion(.success(results))
}
}.resume()
}
And you'd call it like so:
你会这样称呼它:
fetchGenres { results in
switch results {
case .success(let genres):
// use genres here, e.g. update model and UI
case .failure(let error):
print(error.localizedDescription)
}
}
// but don't try to use genres here, as the above runs asynchronously
Note, above I'm dispatching the completion handler back to the main queue to simplify model and UI updates. Some developers take exception to this practice and either use whatever queue URLSession
used or use their own queue (requiring the caller to manually synchronize the results themselves).
请注意,上面我将完成处理程序分派回主队列以简化模型和 UI 更新。一些开发人员反对这种做法,要么使用所使用的任何队列,要么使用URLSession
自己的队列(要求调用者自己手动同步结果)。
But that's not material here. The key issue is the use of completion handler to specify the block of code to be run when the asynchronous request is done.
但这在这里并不重要。关键问题是使用完成处理程序来指定在异步请求完成时要运行的代码块。
The older, Swift 4 pattern is:
较旧的 Swift 4 模式是:
func fetchGenres(completion: @escaping ([Genre]?, Error?) -> Void) {
...
URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error {
DispatchQueue.main.async {
completion(nil, error)
}
return
}
// parse response here
let results = ...
DispatchQueue.main.async {
completion(results, error)
}
}.resume()
}
And you'd call it like so:
你会这样称呼它:
fetchGenres { genres, error in
guard let genres = genres, error == nil else {
// handle failure to get valid response here
return
}
// use genres here
}
// but don't try to use genres here, as the above runs asynchronously
Note, above I retired the use of NSArray
(we don't use those bridged Objective-C typesany more). I assume that we had a Genre
type and we presumably used JSONDecoder
, rather than JSONSerialization
, to decode it. But this question didn't have enough information about the underlying JSON to get into the details here, so I omitted that to avoid clouding the core issue, the use of closures as completion handlers.
请注意,上面我NSArray
不再使用 (我们不再使用那些桥接的 Objective-C 类型)。我假设我们有一个Genre
类型,我们大概使用JSONDecoder
,而不是JSONSerialization
来解码它。但是这个问题没有足够的关于底层 JSON 的信息来深入了解这里的细节,所以我省略了它以避免混淆核心问题,使用闭包作为完成处理程序。
回答by Nebojsa Nadj
Swift 3 version of @Alexey Globchastyy's answer:
Swift 3 版本的@Alexey Globchastyy 的回答:
class func getGenres(completionHandler: @escaping (genres: NSArray) -> ()) {
...
let task = session.dataTask(with:url) {
data, response, error in
...
resultsArray = results
completionHandler(genres: resultsArray)
}
...
task.resume()
}
回答by LironXYZ
I hope you're not still stuck on this, but the short answer is that you can't do this in Swift.
我希望你不会仍然停留在这个问题上,但简短的回答是你不能在 Swift 中做到这一点。
An alternative approach would be to return a callback that will provide the data you need as soon as it is ready.
另一种方法是返回一个回调,该回调将在准备好后立即提供您需要的数据。
回答by IRANNA SALUNKE
There are 3 ways of creating call back functions namely: 1. Completion handler 2. Notification 3. Delegates
创建回调函数有 3 种方式,即: 1. 完成处理程序 2. 通知 3. 委托
Completion HandlerInside set of block is executed and returned when source is available, Handler will wait until response comes so that UI can be updated after.
Completion Handler当源可用时执行并返回内部块集,Handler 将等待直到响应到来,以便之后可以更新 UI。
NotificationBunch of information is triggered over all the app, Listner can retrieve n make use of that info. Async way of getting info through out the project.
通知信息在所有应用程序上触发,Listner 可以检索 n 使用该信息。通过项目获取信息的异步方式。
DelegatesSet of methods will get triggered when delegate is been called, Source must be provided via methods itself
委托被调用时将触发一组方法,必须通过方法本身提供源
回答by TalBenAsulo
Use completion blocks and activate then on the main thread.
使用完成块然后在主线程上激活。
The main thread is the UI thread, whenever you make an async task and you want to update the UI you must do all the UI changes on the UI thread
主线程是 UI 线程,每当您执行异步任务并且想要更新 UI 时,您都必须在 UI 线程上进行所有 UI 更改
example:
例子:
func asycFunc(completion: () -> Void) {
URLSession.shared.dataTask(with: request) { data, _, error in
// This is an async task...!!
if let error = error {
}
DispatchQueue.main.async(execute: { () -> Void in
//When the async taks will be finished this part of code will run on the main thread
completion()
})
}
}
回答by Shubham Mishra
There are mainly 3 ways of achieving callback in swift
swift中实现回调主要有3种方式
Closures/Completion handler
Delegates
Notifications
关闭/完成处理程序
代表
通知
Observers can also be used to get notified once the async task has been completed.
一旦异步任务完成,观察者也可用于获得通知。
回答by CrazyPro007
self.urlSession.dataTask(with: request, completionHandler: { (data, response, error) in
self.endNetworkActivity()
var responseError: Error? = error
// handle http response status
if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode > 299 , httpResponse.statusCode != 422 {
responseError = NSError.errorForHTTPStatus(httpResponse.statusCode)
}
}
var apiResponse: Response
if let _ = responseError {
apiResponse = Response(request, response as? HTTPURLResponse, responseError!)
self.logError(apiResponse.error!, request: request)
// Handle if access token is invalid
if let nsError: NSError = responseError as NSError? , nsError.code == 401 {
DispatchQueue.main.async {
apiResponse = Response(request, response as? HTTPURLResponse, data!)
let message = apiResponse.message()
// Unautorized access
// User logout
return
}
}
else if let nsError: NSError = responseError as NSError? , nsError.code == 503 {
DispatchQueue.main.async {
apiResponse = Response(request, response as? HTTPURLResponse, data!)
let message = apiResponse.message()
// Down time
// Server is currently down due to some maintenance
return
}
}
} else {
apiResponse = Response(request, response as? HTTPURLResponse, data!)
self.logResponse(data!, forRequest: request)
}
self.removeRequestedURL(request.url!)
DispatchQueue.main.async(execute: { () -> Void in
completionHandler(apiResponse)
})
}).resume()