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 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...
}
}
6 个解决方案
#1
58
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)")
}
}
#2
10
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).
斯威夫特已经提供了未来,这是一个承诺的基本组成部分。未来是一个不会失败的承诺(这里的所有术语都基于Scala解释,其中承诺是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风格的承诺(我可能会自己写出来;我相信其他PRs也会受到欢迎;在已经有了未来的情况下,这并不困难)。
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:
在您的特定情况下,我可能会创建一个结果<[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. - 我不建议在get in Swift中加上前缀。它将破坏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
. - 我建议在将结果作为未来返回之前,一直解析到Book对象。这个系统有几种可能会失败的方式,如果你在将来完成这些事情之前检查一下,会更方便。在您的Swift代码的其余部分中,使用[Book]要比使用NSArray要好得多。
#3
8
Another example:
另一个例子:
class func getExchangeRate(#baseCurrency: String, foreignCurrency:String, completion: ((result:Double?) -> Void)!){
let baseURL = kAPIEndPoint
let query = String(baseCurrency)+"_"+String(foreignCurrency)
var finalExchangeRate = 0.0
if let url = NSURL(string: baseURL + query) {
NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in
if ((data) != nil) {
let jsonDictionary:NSDictionary = NSJSONSerialization.JSONObjectWithData(data!, options: nil, error: nil) as NSDictionary
if let results = jsonDictionary["results"] as? NSDictionary{
if let queryResults = results[query] as? NSDictionary{
if let exchangeRate = queryResults["val"] as? Double{
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
dispatch_async(dispatch_get_main_queue()) {
completion(result: exchangeRate)
}
}
}
}
}
}
else {
completion(result: nil)
}
}.resume()
}
}
Call:
电话:
Currency.getExchangeRate(baseCurrency: "USD", foreignCurrency: "EUR") { (result) -> Void in
if let exchangeValue = result {
print(exchangeValue)
}
}
#4
3
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()
}
#5
0
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()
#6
0
Swift 4.0
斯威夫特4.0
For async Request-Response you can user completion handler. See below i had modified you 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. Simple
可以这样调用这个函数。简单的
getGenres { (array) in
// Do operation with your array
}
#1
58
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)")
}
}
#2
10
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).
斯威夫特已经提供了未来,这是一个承诺的基本组成部分。未来是一个不会失败的承诺(这里的所有术语都基于Scala解释,其中承诺是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风格的承诺(我可能会自己写出来;我相信其他PRs也会受到欢迎;在已经有了未来的情况下,这并不困难)。
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:
在您的特定情况下,我可能会创建一个结果<[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. - 我不建议在get in Swift中加上前缀。它将破坏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
. - 我建议在将结果作为未来返回之前,一直解析到Book对象。这个系统有几种可能会失败的方式,如果你在将来完成这些事情之前检查一下,会更方便。在您的Swift代码的其余部分中,使用[Book]要比使用NSArray要好得多。
#3
8
Another example:
另一个例子:
class func getExchangeRate(#baseCurrency: String, foreignCurrency:String, completion: ((result:Double?) -> Void)!){
let baseURL = kAPIEndPoint
let query = String(baseCurrency)+"_"+String(foreignCurrency)
var finalExchangeRate = 0.0
if let url = NSURL(string: baseURL + query) {
NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in
if ((data) != nil) {
let jsonDictionary:NSDictionary = NSJSONSerialization.JSONObjectWithData(data!, options: nil, error: nil) as NSDictionary
if let results = jsonDictionary["results"] as? NSDictionary{
if let queryResults = results[query] as? NSDictionary{
if let exchangeRate = queryResults["val"] as? Double{
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
dispatch_async(dispatch_get_main_queue()) {
completion(result: exchangeRate)
}
}
}
}
}
}
else {
completion(result: nil)
}
}.resume()
}
}
Call:
电话:
Currency.getExchangeRate(baseCurrency: "USD", foreignCurrency: "EUR") { (result) -> Void in
if let exchangeValue = result {
print(exchangeValue)
}
}
#4
3
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()
}
#5
0
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()
#6
0
Swift 4.0
斯威夫特4.0
For async Request-Response you can user completion handler. See below i had modified you 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. Simple
可以这样调用这个函数。简单的
getGenres { (array) in
// Do operation with your array
}