JSON API asynchron ansprechen?

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • JSON API asynchron ansprechen?

    Liebes Forum,

    ich versuche derzeit ein JSON abzurufen - die URL zur JSON funktioniert grundsätzlich (teste ich sie im Browser, kommt alles wie erwartet an), lediglich meine Swift-Funktion spuckt "no data" aus:

    Quellcode

    1. func getJSON(urlToRequest: String) -> NSData{
    2. let request = NSData(contentsOfURL: NSURL(string: urlToRequest)!)
    3. if request != nil {
    4. println("data received")
    5. return request!
    6. } else {
    7. println("no data")
    8. return NSData()
    9. }
    10. }
    Als Anfänger ist mir nicht ganz klar, was hier falsch läuft: Ich vermute aber, dass die URL zur JSON nicht sofort Daten ausgibt, sondern einen Moment braucht, um meine Anfrage zu verarbeiten? Demnach müsste ich die Daten asynchron abrufen bzw der Funktion beibringen, auf die Daten zu warten? Wie gehe ich das an?

    Lieben Dank vorab und Grüße
    Martin
  • Du solltest den Download lieber über NSURLSession machen. Der läuft er asynchron ab:

    Quellcode

    1. NSURLSessionDataTask *theTask = [[NSURLSession sharedSession] dataTaskWithRequest:theRequest completionHandler:^(NSData *inData, NSURLResponse *inResponse, NSError *inError) {
    2. // Daten oder Fehler verarbeiten
    3. }];
    4. [theTask resume];
    Ein synchroner Aufruf blockiert den Thread derApp, in dem er läuft. Das ist häufig dann der Main-Thread. Wenn das länger als 1 Sekunde dauert, kann das zur Ablehnung im App-Store führen.
    „Meine Komplikation hatte eine Komplikation.“
  • Was haltet ihr von folgender Lösung?

    Quellcode

    1. typealias CompletionBlock = (NSData!, NSURLResponse!, NSError!) -> Void
    2. func loadURL(targetUrl: String, completion: CompletionBlock){
    3. let target = NSURL(string: targetUrl)
    4. let request = NSURLRequest(URL: target!)
    5. let session = NSURLSession.sharedSession()
    6. let task = session.dataTaskWithRequest(request, completionHandler: completion)
    7. task.resume()
    8. }
    Alles anzeigen

    Quellcode

    1. let targetURL = "http://google.de"
    2. loadURL(targetURL, { (responseData: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
    3. if responseData != nil{
    4. var sourceCode = NSString(data: responseData, encoding: NSASCIIStringEncoding)
    5. println(responseData)
    6. }
    7. if response != nil { println(response) }
    8. if error != nil{ println(error) }
    9. })
    Alles anzeigen
  • Ein weiterer Ansatz:

    Quellcode

    1. func getURLdata(url: String) -> NSData? {
    2. let target = NSURL(string: url)
    3. var data = NSData(contentsOfURL: target!)
    4. return data
    5. }

    Auf meiner Suche stieß ich darauf, dass NSData(contentsOfURL:) offenbar genau leistet, was ich suche? Zumindest fast: Es scheint etwas wie ein timeout zu geben? Kann ich das irgendwie steuern?

    Überhaupt crasht die Funktion oben, wenn keine Daten empfangen werden. Freue mich über Unterstützung!
  • Entschuldigt - ja natürlich. Habe die vergangenen Tage so viel Zeit damit verbracht, dass ich den Beginn des Threads aus den Augen verlor. Sorry.

    An der von mir genannten asynchronen Methode (siehe oben) stört mich, dass alle weiteren Verarbeitungsschritte innerhalb der loadURL function stehen und ich aus dieser keine Variablen auf höherer Ebene (?) befüllen kann.
  • Also meines Wissens nach kannst Du aus Blocks heraus auf Variablen des aktuellen Scopes zugreifen ... ich bin nicht sicher, was Du mit "höherer Ebene" meinst. Laut Apple Doku:

    Apple Doku schrieb:

    You can reference three standard types of variable, just as you would from a function:
    • Global variables, including static locals
    • Global functions (which aren’t technically variables)
    • Local variables and parameters from an enclosing scope


    Ich rufe z. B. in Completion-Handlern eigene Methoden der aktuellen Klasse auf ... ohne Zugriff auf z. B. self dürfte das kaum funktionieren. So könntest Du Deine weiteren Verarbeitungsschritte z. B. auch in eine eigene Methode verlagern und dieser beim Aufruf die empfangenen Daten als Parameter übergeben.

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Ergänzende Frage: Ich habe nun eine Funktion erstellt, in die ich ein paar Eingabewerte übergebe und die diese dann in eine URL umwandelt, von der Daten abgerufen werden. Als Rückgabewert der Funktion hätte ich nun gern die empfangenen Daten.

    Problem: Am Ende der Funktion muss ich return angeben, doch die Funktion selbst läuft schneller durch als die asynchon angeforderten Daten angekommen sind. Ergebnis: Die Funktion gibt momentan immer nur die initialen Werte aus.

    Wie sieht hier die best practice aus?

    Danke euch für euren Support
  • Könnt ihr mich bitte nochmal unterstützen? Bekomme es leider nicht hin... :(

    Die Funktion:

    Quellcode

    1. typealias CompletionBlock = (NSData!, NSURLResponse!, NSError!) -> NSData
    2. func requestURL(targetUrl: String, httpMethod: String, httpBody: String, completion: CompletionBlock){
    3. // REQUEST
    4. let target = NSURL(string: targetUrl) // URL
    5. let request = NSMutableURLRequest(URL: target!) // REQUEST
    6. // HTTP-METHOD
    7. var method = httpMethod
    8. if method != "POST" { method = "GET" } // STANDARD: GET
    9. request.HTTPMethod = method
    10. // HTTP-BODY
    11. let body = httpBody
    12. if body != "" {
    13. request.HTTPBody = body.dataUsingEncoding(NSUTF8StringEncoding)
    14. }
    15. // NSURLSession
    16. let session = NSURLSession.sharedSession()
    17. let task = session.dataTaskWithRequest(request, completionHandler: completion)
    18. task.resume()
    19. }
    Alles anzeigen


    Der Aufruf:

    Quellcode

    1. requestURL(targetURL, "GET", "", { (responseData: NSData!, response: NSURLResponse!, error: NSError!) -> NSData in
    2. if responseData != nil {
    3. let resultString = NSString(data: responseData, encoding: NSUTF8StringEncoding) // NSASCIIStringEncoding
    4. println("DATA:\n\(resultString)")
    5. }
    6. if error != nil {
    7. println("ERROR:\n\(error)")
    8. }
    9. return responseData
    10. })
    Alles anzeigen
    Kompilieren geht nicht, da in der Funktion in Zeile 21 gemeldet wird: Cannot invoke 'dataTaskWithRequest' with an argument list of type '(NSMutableURLRequest, completionHandler: completionBlock)


    Was mache ich falsch?
  • Der Fehler ist eigentlich recht aufschlussreich.
    Doku für dataTaskWithRequest anschauen:

    Ich würde es so machen:

    Quellcode

    1. let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
    2. data, response, error in
    3. // call completionHandler
    Warum du als Parameter und Rückgabewert NSData hast, ist mir auch ein Rätsel.
  • Hatte dieses Problem zwischenzeitlich gelöst. Unabhängig davon: Mit dem Update auf Swift 2 hat sich der Aufbau nochmal leicht geändert:
    typealias CompletionBlock = (NSData?, NSURLResponse?, NSError?) -> Void

    Ergänzende Frage: Besteht auf diesem Wege die Möglichkeit, abzufragen, wie viel Daten schon empfangen worden sind, und wie viele insgesamt erwartet werden (um eine Fortschrittsanzeige zu realisieren)?