从Swift函数中的异步调用返回数据

Mar*_*ers 83 rest asynchronous ios swift

我在Swift项目中创建了一个实用程序类来处理所有REST请求和响应.我已经构建了一个简单的REST API,因此我可以测试我的代码.我创建了一个需要返回NSArray的类方法,但由于API调用是异步的,我需要从异步调用中的方法返回.问题是异步返回void.如果我在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...
    }
}
Run Code Online (Sandbox Code Playgroud)

Ale*_*tyy 86

您可以传递回调,并在异步调用中调用回调

就像是:

class func getGenres(completionHandler: (genres: NSArray) -> ()) {
    ...
    let task = session.dataTaskWithURL(url) {
        data, response, error in
        ...
        resultsArray = results
        completionHandler(genres: resultsArray)
    }
    ...
    task.resume()
}
Run Code Online (Sandbox Code Playgroud)

然后调用此方法:

override func viewDidLoad() {
    Bookshop.getGenres {
        genres in
        println("View Controller: \(genres)")     
    }
}
Run Code Online (Sandbox Code Playgroud)


Rob*_*ier 11

Swiftz已经提供了Future,这是Promise的基本构建块.未来是一个不能失败的承诺(这里的所有术语都基于Scala解释,其中Promise是Monad).

https://github.com/maxpow4h/swiftz/blob/master/swiftz/Future.swift

希望最终会扩展到完整的Scala风格的Promise(我可能会在某个时候自己编写;我相信其他的PR会受到欢迎;并且Future已经到位并不困难).

在你的特殊情况下,我可能会创建一个Result<[Book]>(基于Alexandros Salazar的版本Result).然后您的方法签名将是:

class func fetchGenres() -> Future<Result<[Book]>> {
Run Code Online (Sandbox Code Playgroud)

笔记

  • 我不建议get在Swift中使用函数前缀.它将破坏与ObjC的某些互操作性.
  • 我建议Book在将结果作为a返回之前一直解析为一个对象Future.这个系统有几种失败的方法,如果在将它们包装成一个之前检查所有这些东西会更方便Future.前往[Book]远为您的银行代码比周围交给其他人更NSArray.

  • 斯威夫特不再支持"未来".但是看看https://github.com/mxcl/PromiseKit它与Swiftz一起工作得很好! (3认同)
  • 听起来"Swiftz"是Swift的第三方功能库.由于您的答案似乎是基于该库,因此您应该明确说明.(例如"有一个名为'Swiftz'的第三方库支持像Futures这样的功能结构,如果你想实现Promises,它应该是一个很好的起点.")否则你的读者只会想知道为什么你拼写错误"迅速". (3认同)
  • 请注意,https://github.com/maxpow4h/swiftz/blob/master/swiftz/Future.swift不再有效. (3认同)

Rob*_*Rob 11

基本模式是使用完成处理程序关闭。

例如,我们经常使用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()
}
Run Code Online (Sandbox Code Playgroud)

你会这样称呼它:

fetchGenres { results in
    switch results {
    case .failure(let error):
        print(error.localizedDescription)

    case .success(let genres):
        // use `genres` here, e.g. update model and UI            
    }
}

// but don’t try to use `genres` here, as the above runs asynchronously
Run Code Online (Sandbox Code Playgroud)

请注意,上面我将完成处理程序分派回主队列以简化模型和 UI 更新。一些开发人员反对这种做法,要么使用所使用的任何队列,要么使用URLSession自己的队列(要求调用者自己手动同步结果)。

但这在这里并不重要。关键问题是使用完成处理程序来指定在异步请求完成时要运行的代码块。


请注意,上面我NSArray不再使用 (我们不再使用那些桥接的 Objective-C 类型)。我假设我们有一个Genre类型,我们大概使用JSONDecoder,而不是JSONSerialization来解码它。但是这个问题没有足够的关于底层 JSON 的信息来深入了解这里的细节,所以我省略了它以避免混淆核心问题,使用闭包作为完成处理程序。


Jay*_*eep 5

迅捷4.0

对于异步请求-响应,您可以使用完成处理程序。参见下文,我使用完成句柄范例修改了解决方案。

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()
    }
Run Code Online (Sandbox Code Playgroud)

您可以按以下方式调用此函数:

getGenres { (array) in
    // Do operation with array
}
Run Code Online (Sandbox Code Playgroud)