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

Ios 从Swift函数中的异步调用返回数据,ios,rest,asynchronous,swift,Ios,Rest,Asynchronous,Swift,我在Swift项目中创建了一个实用程序类,用于处理所有REST请求和响应。我已经构建了一个简单的RESTAPI,所以我可以测试我的代码。我创建了一个需要返回NSArray的类方法,但是因为API调用是异步的,所以我需要从异步调用中的方法返回。问题是异步返回void。 如果我在Node中这样做,我会使用JS承诺,但我无法找到一个在Swift中工作的解决方案 import Foundation class Bookshop { class func getGenres() -> NS

我在Swift项目中创建了一个实用程序类,用于处理所有REST请求和响应。我已经构建了一个简单的RESTAPI,所以我可以测试我的代码。我创建了一个需要返回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...
    }
}

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

比如:

class func getGenres(completionHandler: (genres: NSArray) -> ()) {
    ...
    let task = session.dataTaskWithURL(url) {
        data, response, error in
        ...
        resultsArray = results
        completionHandler(genres: resultsArray)
    }
    ...
    task.resume()
}
然后调用此方法:

override func viewDidLoad() {
    Bookshop.getGenres {
        genres in
        println("View Controller: \(genres)")     
    }
}

斯威夫茨已经提供了未来,这是承诺的基本组成部分。未来是一个不能失败的承诺(这里的所有术语都基于Scala的解释,)

希望最终能够扩展到一个完整的Scala风格的承诺(我可能会在某个时候自己编写;我相信其他PRs也会受到欢迎;未来已经存在,这并不难)

在您的特定情况下,我可能会创建一个
结果
(基于)。那么您的方法签名将是:

class func fetchGenres() -> Future<Result<[Book]>> {
class func fetchGenres()->Future{
注释

  • 我不建议在Swift中使用
    get
    作为函数前缀。它会破坏与ObjC的某些互操作性
  • 我建议在将结果作为
    未来
    返回之前,先解析一个
    书籍
    对象。这个系统有几种可能失败的方法,如果在将它们包装成
    未来
    之前检查所有这些内容,会更方便。进入
    [书籍]
    对于Swift代码的其余部分来说比交给NSArray要好得多

Swift 3版@Alexey Globchasty的答案:

class func getGenres(completionHandler: @escaping (genres: NSArray) -> ()) {
...
let task = session.dataTask(with:url) {
    data, response, error in
    ...
    resultsArray = results
    completionHandler(genres: resultsArray)
}
...
task.resume()
}

Swift 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()
    }
您可以按如下方式调用此函数:

getGenres { (array) in
    // Do operation with array
}

我希望你还没有陷入这个问题,但简单的回答是,你不能在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...
    }
}

另一种方法是返回一个回调,该回调将在数据准备好后立即提供所需的数据。

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

例如,我们经常使用
结果

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()
}
注意,上面我将完成处理程序分派回主队列以简化模型和UI更新。一些开发人员对此做法持异议,要么使用使用的任何队列
URLSession
,要么使用自己的队列(要求调用方自己手动同步结果)

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


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

在swift中实现回调的方法主要有3种
  • 闭包/完成处理程序

  • 代表

  • 通知


  • 异步任务完成后,还可以使用观察者获得通知。

    创建回调函数有3种方法,即: 1.完成处理程序 2.通知 3.代表

    完成处理程序 当源可用时,在块集内部执行并返回,处理程序将等待响应到来,以便在响应之后可以更新UI

    通知 所有应用程序都会触发一堆信息,Listner可以检索并利用这些信息。异步方式通过项目获取信息

    代表
    调用委托时将触发一组方法,源必须通过方法本身提供

    有一些非常通用的要求,希望每个好的API管理器都能满足: 将实现面向协议的API客户端。

    APIClient初始接口

    现在请检查完整的api结构

    // ******* This is API Call Class  *****
    public typealias ResultCallback<Value> = (Result<Value, Error>) -> Void
    
    /// Implementation of a generic-based  API client
    public class APIClient {
        private let baseEndpointUrl = URL(string: "irl")!
        private let session = URLSession(configuration: .default)
    
        public init() {
    
        }
    
        /// Sends a request to servers, calling the completion method when finished
        public func send<T: APIRequest>(_ request: T, completion: @escaping ResultCallback<DataContainer<T.Response>>) {
            let endpoint = self.endpoint(for: request)
    
            let task = session.dataTask(with: URLRequest(url: endpoint)) { data, response, error in
                if let data = data {
                    do {
                        // Decode the top level response, and look up the decoded response to see
                        // if it's a success or a failure
                        let apiResponse = try JSONDecoder().decode(APIResponse<T.Response>.self, from: data)
    
                        if let dataContainer = apiResponse.data {
                            completion(.success(dataContainer))
                        } else if let message = apiResponse.message {
                            completion(.failure(APIError.server(message: message)))
                        } else {
                            completion(.failure(APIError.decoding))
                        }
                    } catch {
                        completion(.failure(error))
                    }
                } else if let error = error {
                    completion(.failure(error))
                }
            }
            task.resume()
        }
    
        /// Encodes a URL based on the given request
        /// Everything needed for a public request to api servers is encoded directly in this URL
        private func endpoint<T: APIRequest>(for request: T) -> URL {
            guard let baseUrl = URL(string: request.resourceName, relativeTo: baseEndpointUrl) else {
                fatalError("Bad resourceName: \(request.resourceName)")
            }
    
            var components = URLComponents(url: baseUrl, resolvingAgainstBaseURL: true)!
    
            // Common query items needed for all api requests
            let timestamp = "\(Date().timeIntervalSince1970)"
            let hash = "\(timestamp)"
            let commonQueryItems = [
                URLQueryItem(name: "ts", value: timestamp),
                URLQueryItem(name: "hash", value: hash),
                URLQueryItem(name: "apikey", value: "")
            ]
    
            // Custom query items needed for this specific request
            let customQueryItems: [URLQueryItem]
    
            do {
                customQueryItems = try URLQueryItemEncoder.encode(request)
            } catch {
                fatalError("Wrong parameters: \(error)")
            }
    
            components.queryItems = commonQueryItems + customQueryItems
    
            // Construct the final URL with all the previous data
            return components.url!
        }
    }
    
    // ******  API Request Encodable Protocol *****
    public protocol APIRequest: Encodable {
        /// Response (will be wrapped with a DataContainer)
        associatedtype Response: Decodable
    
        /// Endpoint for this request (the last part of the URL)
        var resourceName: String { get }
    }
    
    // ****** This Results type  Data Container Struct ******
    public struct DataContainer<Results: Decodable>: Decodable {
        public let offset: Int
        public let limit: Int
        public let total: Int
        public let count: Int
        public let results: Results
    }
    // ***** API Errro Enum ****
    public enum APIError: Error {
        case encoding
        case decoding
        case server(message: String)
    }
    
    
    // ****** API Response Struct ******
    public struct APIResponse<Response: Decodable>: Decodable {
        /// Whether it was ok or not
        public let status: String?
        /// Message that usually gives more information about some error
        public let message: String?
        /// Requested data
        public let data: DataContainer<Response>?
    }
    
    // ***** URL Query Encoder OR JSON Encoder *****
    enum URLQueryItemEncoder {
        static func encode<T: Encodable>(_ encodable: T) throws -> [URLQueryItem] {
            let parametersData = try JSONEncoder().encode(encodable)
            let parameters = try JSONDecoder().decode([String: HTTPParam].self, from: parametersData)
            return parameters.map { URLQueryItem(name: $0, value: $1.description) }
        }
    }
    
    // ****** HTTP Pamater Conversion Enum *****
    enum HTTPParam: CustomStringConvertible, Decodable {
        case string(String)
        case bool(Bool)
        case int(Int)
        case double(Double)
    
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
    
            if let string = try? container.decode(String.self) {
                self = .string(string)
            } else if let bool = try? container.decode(Bool.self) {
                self = .bool(bool)
            } else if let int = try? container.decode(Int.self) {
                self = .int(int)
            } else if let double = try? container.decode(Double.self) {
                self = .double(double)
            } else {
                throw APIError.decoding
            }
        }
    
        var description: String {
            switch self {
            case .string(let string):
                return string
            case .bool(let bool):
                return String(describing: bool)
            case .int(let int):
                return String(describing: int)
            case .double(let double):
                return String(describing: double)
            }
        }
    }
    
    /// **** This is your API Request Endpoint  Method in Struct *****
    public struct GetCharacters: APIRequest {
        public typealias Response = [MyCharacter]
    
        public var resourceName: String {
            return "characters"
        }
    
        // Parameters
        public let name: String?
        public let nameStartsWith: String?
        public let limit: Int?
        public let offset: Int?
    
        // Note that nil parameters will not be used
        public init(name: String? = nil,
                    nameStartsWith: String? = nil,
                    limit: Int? = nil,
                    offset: Int? = nil) {
            self.name = name
            self.nameStartsWith = nameStartsWith
            self.limit = limit
            self.offset = offset
        }
    }
    
    // *** This is Model for Above Api endpoint method ****
    public struct MyCharacter: Decodable {
        public let id: Int
        public let name: String?
        public let description: String?
    }
    
    
    // ***** These below line you used to call any api call in your controller or view model ****
    func viewDidLoad() {
        let apiClient = APIClient()
    
        // A simple request with no parameters
        apiClient.send(GetCharacters()) { response in
    
            response.map { dataContainer in
                print(dataContainer.results)
            }
        }
    
    }
    
    //*******这是API调用类*****
    公共类型别名ResultCallback=(结果)->Void
    ///基于泛型的API客户端的实现
    公共类客户端{
    私有let baseEndpointUrl=URL(字符串:“irl”)!
    private let session=URLSession(配置:。默认值)
    公共init(){
    }
    ///向服务器发送请求,完成后调用完成方法
    public func send(uu请求:T,完成:@escaping ResultCallback){
    让endpoint=self.endpoint(for:request)
    让task=session.dataTask(with:URLRequest(url:endpoint)){data,response,中的错误
    如果let data=data{
    做{
    //解码顶层响应,并查找解码后的响应以查看
    //是成功还是失败
    让apiResponse=try JSONDecoder().decode(apiResponse.self,from:data)
    如果let dataContainer=apiResponse.data{
    完成(.success(数据容器))
    }如果let message=apiResponse.message,则为else{
    完成(.failure(apirerror.server(message:message)))
    }否则{
    完成(.failure(APIError.decoding))
    }
    }抓住{
    完成(.failure(error))
    }
    }否则,如果let error=error{
    完成(.failure(error))
    }
    }
    task.resume()
    }
    
    protocol APIClient {
       func send(_ request: APIRequest,
                  completion: @escaping (APIResponse?, Error?) -> Void) 
    }
    
    protocol APIRequest: Encodable {
        var resourceName: String { get }
    }
    
    protocol APIResponse: Decodable {
    }
    
    // ******* This is API Call Class  *****
    public typealias ResultCallback<Value> = (Result<Value, Error>) -> Void
    
    /// Implementation of a generic-based  API client
    public class APIClient {
        private let baseEndpointUrl = URL(string: "irl")!
        private let session = URLSession(configuration: .default)
    
        public init() {
    
        }
    
        /// Sends a request to servers, calling the completion method when finished
        public func send<T: APIRequest>(_ request: T, completion: @escaping ResultCallback<DataContainer<T.Response>>) {
            let endpoint = self.endpoint(for: request)
    
            let task = session.dataTask(with: URLRequest(url: endpoint)) { data, response, error in
                if let data = data {
                    do {
                        // Decode the top level response, and look up the decoded response to see
                        // if it's a success or a failure
                        let apiResponse = try JSONDecoder().decode(APIResponse<T.Response>.self, from: data)
    
                        if let dataContainer = apiResponse.data {
                            completion(.success(dataContainer))
                        } else if let message = apiResponse.message {
                            completion(.failure(APIError.server(message: message)))
                        } else {
                            completion(.failure(APIError.decoding))
                        }
                    } catch {
                        completion(.failure(error))
                    }
                } else if let error = error {
                    completion(.failure(error))
                }
            }
            task.resume()
        }
    
        /// Encodes a URL based on the given request
        /// Everything needed for a public request to api servers is encoded directly in this URL
        private func endpoint<T: APIRequest>(for request: T) -> URL {
            guard let baseUrl = URL(string: request.resourceName, relativeTo: baseEndpointUrl) else {
                fatalError("Bad resourceName: \(request.resourceName)")
            }
    
            var components = URLComponents(url: baseUrl, resolvingAgainstBaseURL: true)!
    
            // Common query items needed for all api requests
            let timestamp = "\(Date().timeIntervalSince1970)"
            let hash = "\(timestamp)"
            let commonQueryItems = [
                URLQueryItem(name: "ts", value: timestamp),
                URLQueryItem(name: "hash", value: hash),
                URLQueryItem(name: "apikey", value: "")
            ]
    
            // Custom query items needed for this specific request
            let customQueryItems: [URLQueryItem]
    
            do {
                customQueryItems = try URLQueryItemEncoder.encode(request)
            } catch {
                fatalError("Wrong parameters: \(error)")
            }
    
            components.queryItems = commonQueryItems + customQueryItems
    
            // Construct the final URL with all the previous data
            return components.url!
        }
    }
    
    // ******  API Request Encodable Protocol *****
    public protocol APIRequest: Encodable {
        /// Response (will be wrapped with a DataContainer)
        associatedtype Response: Decodable
    
        /// Endpoint for this request (the last part of the URL)
        var resourceName: String { get }
    }
    
    // ****** This Results type  Data Container Struct ******
    public struct DataContainer<Results: Decodable>: Decodable {
        public let offset: Int
        public let limit: Int
        public let total: Int
        public let count: Int
        public let results: Results
    }
    // ***** API Errro Enum ****
    public enum APIError: Error {
        case encoding
        case decoding
        case server(message: String)
    }
    
    
    // ****** API Response Struct ******
    public struct APIResponse<Response: Decodable>: Decodable {
        /// Whether it was ok or not
        public let status: String?
        /// Message that usually gives more information about some error
        public let message: String?
        /// Requested data
        public let data: DataContainer<Response>?
    }
    
    // ***** URL Query Encoder OR JSON Encoder *****
    enum URLQueryItemEncoder {
        static func encode<T: Encodable>(_ encodable: T) throws -> [URLQueryItem] {
            let parametersData = try JSONEncoder().encode(encodable)
            let parameters = try JSONDecoder().decode([String: HTTPParam].self, from: parametersData)
            return parameters.map { URLQueryItem(name: $0, value: $1.description) }
        }
    }
    
    // ****** HTTP Pamater Conversion Enum *****
    enum HTTPParam: CustomStringConvertible, Decodable {
        case string(String)
        case bool(Bool)
        case int(Int)
        case double(Double)
    
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
    
            if let string = try? container.decode(String.self) {
                self = .string(string)
            } else if let bool = try? container.decode(Bool.self) {
                self = .bool(bool)
            } else if let int = try? container.decode(Int.self) {
                self = .int(int)
            } else if let double = try? container.decode(Double.self) {
                self = .double(double)
            } else {
                throw APIError.decoding
            }
        }
    
        var description: String {
            switch self {
            case .string(let string):
                return string
            case .bool(let bool):
                return String(describing: bool)
            case .int(let int):
                return String(describing: int)
            case .double(let double):
                return String(describing: double)
            }
        }
    }
    
    /// **** This is your API Request Endpoint  Method in Struct *****
    public struct GetCharacters: APIRequest {
        public typealias Response = [MyCharacter]
    
        public var resourceName: String {
            return "characters"
        }
    
        // Parameters
        public let name: String?
        public let nameStartsWith: String?
        public let limit: Int?
        public let offset: Int?
    
        // Note that nil parameters will not be used
        public init(name: String? = nil,
                    nameStartsWith: String? = nil,
                    limit: Int? = nil,
                    offset: Int? = nil) {
            self.name = name
            self.nameStartsWith = nameStartsWith
            self.limit = limit
            self.offset = offset
        }
    }
    
    // *** This is Model for Above Api endpoint method ****
    public struct MyCharacter: Decodable {
        public let id: Int
        public let name: String?
        public let description: String?
    }
    
    
    // ***** These below line you used to call any api call in your controller or view model ****
    func viewDidLoad() {
        let apiClient = APIClient()
    
        // A simple request with no parameters
        apiClient.send(GetCharacters()) { response in
    
            response.map { dataContainer in
                print(dataContainer.results)
            }
        }
    
    }
    
    func testUrlSession(urlStr:String, completionHandler: @escaping ((String) -> Void)) {
            let url = URL(string: urlStr)!
    
    
            let task = URLSession.shared.dataTask(with: url){(data, response, error) in
                guard let data = data else { return }
                if let strContent = String(data: data, encoding: .utf8) {
                completionHandler(strContent)
                }
            }
    
    
            task.resume()
        }
    
    testUrlSession(urlStr: "YOUR-URL") { (value) in
                print("Your string value ::- \(value)")
    }