webentwicklung-frage-antwort-db.com.de

Rückgabe von Daten von einem asynchronen Anruf in der Swift-Funktion

Ich habe in meinem Swift-Projekt eine Dienstprogrammklasse erstellt, die alle REST -Anfragen und -Antworten verarbeitet. Ich habe eine einfache REST - API entwickelt, mit der ich meinen Code testen kann. Ich habe eine Klassenmethode erstellt, die ein NSArray zurückgeben muss, aber da der API-Aufruf async ist, muss ich von der Methode innerhalb des async-Aufrufs zurückkehren. Das Problem ist, dass das async-Ergebnis void ist .. Wenn ich dies in Node machen würde, würde ich JS-Versprechen verwenden, aber ich kann keine Lösung finden, die in Swift funktioniert.

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...
    }
}
61
Mark Tyers

Sie können einen Rückruf übergeben und einen Rückruf innerhalb eines asynchronen Anrufs anrufen

so etwas wie:

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

und dann diese Methode aufrufen:

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

Swiftz bietet bereits Future an, den Grundbaustein eines Versprechens. Eine Zukunft ist ein Versprechen, das nicht scheitern kann (alle Begriffe hier basieren auf der Scala-Interpretation, wobei ein Versprechen eine Monade ist ).

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

Hoffentlich wird sich das Programm letztendlich zu einem vollständigen Scala-Stilversprechen ausdehnen (ich schreibe es vielleicht selbst zu einem bestimmten Zeitpunkt; ich bin mir sicher, dass andere PRs willkommen sind; es ist nicht so schwierig, wenn Future bereits installiert ist).

In Ihrem speziellen Fall würde ich wahrscheinlich einen Result<[Book]> (basierend auf Alexandros Salazars Version von Result ) erstellen. Dann wäre Ihre Methodensignatur:

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

Anmerkungen

  • Ich empfehle nicht, Funktionen mit get in Swift vorzustellen. Bestimmte Arten der Interoperabilität mit ObjC werden dadurch beeinträchtigt.
  • Ich empfehle, bis zu einem Book-Objekt zu analysieren, bevor Sie Ihre Ergebnisse als Future zurückgeben. Es gibt mehrere Möglichkeiten, wie dieses System ausfallen kann, und es ist viel praktischer, wenn Sie nach all diesen Dingen suchen, bevor Sie sie in eine Future packen. [Book] ist für den Rest Ihres Swift-Codes viel besser, als eine NSArray zu übergeben.
10
Rob Napier

Swift 4.0

Für async-Request-Response können Sie einen Completion-Handler verwenden. Siehe unten Ich habe die Lösung mit dem Completion-Handle-Paradigma modifiziert. 

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()
    }

Sie können diese Funktion wie folgt aufrufen:

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

Swift 3-Version von @Alexey Globchastyys Antwort:

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

Ich hoffe, dass Sie nicht immer noch dabei sind, aber die kurze Antwort ist, dass Sie dies in Swift nicht tun können.

Ein alternativer Ansatz wäre die Rückgabe eines Rückrufs, der die benötigten Daten bereitstellt, sobald diese bereit sind.

2
LironXYZ

Das grundlegende Muster besteht darin, Abschluss-Handler-Schließungen zu verwenden. 

Im kommenden Swift 5 würden Sie beispielsweise Result verwenden:

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()
}

Und du würdest es so nennen:

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

Beachten Sie, dass ich oben den Beendigungs-Handler zurück in die Hauptwarteschlange schicke, um Modell- und Benutzeroberflächenaktualisierungen zu vereinfachen. Einige Entwickler machen eine Ausnahme von dieser Vorgehensweise und verwenden entweder die verwendete Warteschlange URLSession oder ihre eigene Warteschlange (der Aufrufer muss die Ergebnisse selbst manuell synchronisieren).

Aber das ist hier nicht wesentlich. Das Hauptproblem ist die Verwendung des Completion-Handlers, um den Codeblock anzugeben, der ausgeführt werden soll, wenn die asynchrone Anforderung ausgeführt wird.


Das ältere Swift 4-Muster lautet:

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()
}

Und du würdest es so nennen:

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

Beachten Sie, oben habe ich die Verwendung von NSArray zurückgezogen (wir verwenden keine diese überbrückten Objective-C-Typen mehr). Ich gehe davon aus, dass wir einen Genre-Typ hatten und vermutlich JSONDecoder anstelle von JSONSerialization verwendet haben, um ihn zu decodieren. Diese Frage hatte jedoch nicht genügend Informationen über die zugrunde liegende JSON, um auf die Details hier eingehen zu können. Ich habe daher darauf verzichtet, das Kernproblem, die Verwendung von Closures, als Abschlusshandler zu vermeiden.

1
Rob

Es gibt drei Möglichkeiten, Rückruffunktionen zu erstellen, nämlich: 1. Completion-Handler 2. Benachrichtigung 3. Delegierte

Completion Handler Der interne Satz des Blocks wird ausgeführt und zurückgegeben, wenn die Quelle verfügbar ist. Der Handler wartet, bis die Antwort kommt, sodass die Benutzeroberfläche danach aktualisiert werden kann.

Benachrichtigung Eine Reihe von Informationen wird über die gesamte App ausgelöst. Listner kann diese Informationen abrufen. Asynchrone Art, Informationen durch das Projekt zu bekommen.

Delegaten Methodensatz wird ausgelöst, wenn Delegat aufgerufen wird. Source muss über Methoden selbst bereitgestellt werden

0
IRANNA SALUNKE

Dies ist ein kleiner Anwendungsfall, der hilfreich sein kann:

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()
    }

Beim Aufruf der Funktion: -

testUrlSession(urlStr: "YOUR-URL") { (value) in
            print("Your string value ::- \(value)")
}
0
a.palo

Es gibt hauptsächlich drei Möglichkeiten, einen Rückruf in Swift zu erreichen

  1. Closures/Completion Handler

  2. Delegierte

  3. Benachrichtigungen

Mit Beobachtern können Sie auch benachrichtigt werden, wenn die asynchrone Task abgeschlossen ist.

0
HSAM

Es gibt einige sehr allgemeine Anforderungen, die jeder gute API-Manager erfüllen muss: Er implementiert einen protokollorientierten API-Client.

APIClient Initial Interface

protocol APIClient {
   func send(_ request: APIRequest,
              completion: @escaping (APIResponse?, Error?) -> Void) 
}

protocol APIRequest: Encodable {
    var resourceName: String { get }
}

protocol APIResponse: Decodable {
}

Überprüfen Sie nun die vollständige API-Struktur

// ******* 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)
        }
    }

}
0
Rashpinder Maan

Verwenden Sie Abschlussblöcke und aktivieren Sie sie anschließend im Hauptthread.

Der Haupt-Thread ist der UI-Thread. Wenn Sie eine asynchrone Aufgabe erstellen und die Benutzeroberfläche aktualisieren möchten, müssen Sie alle Änderungen an der Benutzeroberfläche des UI-Threads vornehmen 

beispiel:

    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()
                })

        }

}
0
TalBenAsulo