2016-01-22 12 views
5

Ich benutze viele Async-Netzwerk-Anfrage (übrigens jede Netzwerkanforderung in iOS muss durch async) und ich finde Weg, Fehler besser von Apples dataTaskWithRequest zu behandeln, die throws nicht unterstützt.Der beste Weg, um Fehler von asynchronen Schließungen in Swift 2 zu behandeln?

Ich habe Code wie folgt aus:

func sendRequest(someData: MyCustomClass?, completion: (response: NSData?) ->()) { 
    let request = NSURLRequest(URL: NSURL(string: "http://google.com")!) 

    if someData == nil { 
     // throw my custom error 
    } 

    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { 
     data, response, error in 

     // here I want to handle Apple's error 
    } 
    task.resume() 
} 

Ich brauche meine mögliche individuelle Fehler zu analysieren und mögliche Fehler bei der Verbindung von dataTaskWithRequest handhaben. Swift 2 führte throws ein, aber Sie können nicht von der Schließung von Apple werfen, weil sie keine Unterstützung für das Werfen haben und Async laufen.

Ich sehe nur Weg, um meine Fertigstellung Block NSError zurückgeben, aber wie ich weiß mit NSError ist alten Stil Objective-C Weg. ErrorType kann nur mit Würfen verwendet werden (afaik).

Was ist die beste und modernste Methode zur Behandlung von Fehlern bei der Verwendung von Apple-Netzwerkverschlüssen? Es gibt keine Möglichkeit, keine Async-Netzwerkfunktionen zu verwenden, wie ich es verstehe.

+0

Sie Errortype ohne wirft verwenden können. d. h. Rückgabe in Ihrem Beendigungshandhaber. Vielleicht möchten Sie einen Blick darauf werfen, wie Alamofire Antworten/Fehler behandelt https: // github.com/Alamofire/Alamofire/Blob/Master/Quelle/Result.swift – doschi

Antwort

13

Es gibt viele Möglichkeiten, wie Sie das lösen können, aber ich würde empfehlen, einen Completion-Block zu verwenden, der eine Result Enum erwartet. Das wäre wahrscheinlich der schnellste Weg.

Das Endergebnis hat genau zwei Zustände, Erfolg und Fehler, was einen großen Vorteil gegenüber den üblichen zwei optionalen Rückgabewerten (Daten und Fehler), die zu 4 möglichen Zuständen führen.

Die Enumeration in einem Completion Block beendet das Puzzle.

let InvalidURLCode = 999 
let NoDataCode = 998 
func getFrom(urlString: String, completion:Result<NSData> -> Void) { 
    // make sure the URL is valid, if not return custom error 
    guard let url = NSURL(string: urlString) else { return completion(.Error("Invalid URL", InvalidURLCode)) } 

    let request = NSURLRequest(URL: url) 
    NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in 
     // if error returned, extract message and code then pass as Result enum 
     guard error == nil else { return completion(.Error(error!.localizedDescription, error!.code)) } 

     // if no data is returned, return custom error 
     guard let data = data else { return completion(.Error("No data returned", NoDataCode)) } 

     // return success 
     completion(.Success(data)) 
    }.resume() 
} 

Da der Rückgabewert eine Aufzählung ist, sollten Sie davon abschalten.

Eine andere Lösung wäre, zwei Abschlussblöcke zu übergeben, einen für Erfolg und einen für Fehler. etwas entlang der Linien von:

func getFrom(urlString: String, successHandler:NSData -> Void, errorHandler:(String, Int) -> Void) 
0

Es gibt eine elegante Methode eine JavaScript-ähnliche Versprechen Bibliothek oder ein Scala-like "Future and Promise" Bibliothek verwendet.

Mit Scala-Stil Futures und verspricht, es kann wie folgt aussehen:

Ihre ursprüngliche Funktion

func sendRequest(someData: MyCustomClass?, completion: (response: NSData?) ->())

kann wie unten gezeigt umgesetzt werden. Es zeigt auch, wie ein Versprechen zu schaffen, kehrt früh mit einer gescheiterten Zukunft und wie zu erfüllen/ein Versprechen ablehnen:

func sendRequest(someData: MyCustomClass) -> Future<NSData> { 
    guard let url = ... else { 
    return Future.failure(MySessionError.InvalidURL) // bail out early with a completed future 
    } 
    let request = ... // setup request 
    let promise = Promise<NSData>() 
    NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in 
    guard let error = error else { 
     promise.reject(error) // Client error 
    } 
    // The following assertions should be true, unless error != nil 
    assert(data != nil) 
    assert(response != nil) 

    // We expect HTTP protocol: 
    guard let response = response! as NSHTTPURLResponse else { 
     promise.reject(MySessionError.ProtocolError) // signal that we expected HTTP. 
    } 

    // Check status code: 
    guard myValidStatusCodeArray.contains(response.statusCode) else { 
     let message: String? = ... // convert the response data to a string, if any and if possible 
     promise.reject(MySessionError.InvalidStatusCode(statusCode: response.statusCode, message: message ?? "")) 
    } 

    // Check MIME type if given: 
    if let mimeType = response.MIMEType { 
     guard myValidMIMETypesArray.contains(mimeType) else { 
     promise.reject(MySessionError.MIMETypeNotAccepted(mimeType: mimeType)) 
     } 
    } else { 
     // If we require a MIMEType - reject the promise. 
    } 
    // transform data to some other object if desired, can be done in a later, too. 

    promise.fulfill(data!) 
    }.resume() 

    return promise.future! 
} 

Sie könnten eine JSON als Antwort erwarten - wenn die Anforderung erfolgreich ist.

Nun könnte man es wie folgt verwenden:

sendRequest(myObject).map { data in 
    return try NSJSONSerialization.dataWithJSONObject(data, options: []) 
} 
.map { object in 
    // the object returned from the step above, unless it failed. 
    // Now, "process" the object: 
    ... 
    // You may throw an error if something goes wrong: 
    if failed { 
     throw MyError.Failed 
    } 
} 
.onFailure { error in 
    // We reach here IFF an error occurred in any of the 
    // previous tasks. 
    // error is of type ErrorType. 
    print("Error: \(error)") 
}