2016-06-04 10 views
0

Ich bin neu in Objective C und iOS Entwicklung im Allgemeinen. Ich versuche, eine App zu erstellen, die eine HTTP-Anfrage machen und den Inhalt auf einem Etikett anzeigen würde.Warte, bis NSURLSessionDataTask zurückkommt

Als ich zu testen begann, bemerkte ich, dass das Etikett leer war, obwohl meine Logs zeigten, dass ich Daten zurück hatte. Scheinbar passiert dies, weil die Antwort nicht bereit ist, wenn der Etikettentext aktualisiert wird.

Ich habe eine Schleife auf die Oberseite, um das zu beheben, aber ich bin mir fast sicher, dass es eine bessere Möglichkeit geben muss, damit umzugehen.

ViewController.m

- (IBAction)buttonSearch:(id)sender { 

    HttpRequest *http = [[HttpRequest alloc] init]; 
    [http sendRequestFromURL: @"https://en.wiktionary.org/wiki/incredible"]; 

    //I put this here to give some time for the url session to comeback. 
    int count; 
    while (http.responseText ==nil) { 
     self.outputLabel.text = [NSString stringWithFormat: @"Getting data %i ", count]; 
    } 

    self.outputLabel.text = http.responseText; 

} 

HttpRequest.h

#import <Foundation/Foundation.h> 

@interface HttpRequest : NSObject 

@property (strong, nonatomic) NSString *responseText; 

- (void) sendRequestFromURL: (NSString *) url; 
- (NSString *) getElementBetweenText: (NSString *) start andText: (NSString *) end; 

@end 

HttpRequest.m

@implementation HttpRequest 

- (void) sendRequestFromURL: (NSString *) url { 

    NSURL *myURL = [NSURL URLWithString: url]; 
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL]; 
    NSURLSession *session = [NSURLSession sharedSession]; 


    NSURLSessionDataTask *task = [session dataTaskWithRequest: request 
              completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 
               self.responseText = [[NSString alloc] initWithData: data 
                          encoding: NSUTF8StringEncoding]; 
              }]; 
    [task resume]; 

} 

Vielen Dank für die Hilfe :)

aktualisiert

hier viel für die sehr nützlichen Kommentare Nach der Lektüre wurde mir klar, dass ich den ganzen Punkt fehlte. Technisch gesehen wird NSURLSessionDataTask einen Task zu einer Warteschlange hinzufügen, die den Aufruf asynchron durchführt, und dann muss ich diesen Aufruf mit einem Codeblock versehen, den ich ausführen möchte, wenn der von der Task erzeugte Thread abgeschlossen wurde.

Duncan bedankt sich sehr für die Antwort und die Kommentare im Code. Das hat mir sehr geholfen zu verstehen.

Also schrieb ich meine Prozeduren mit den bereitgestellten Informationen um. Beachten Sie, dass sie ein wenig wortreich sind, aber ich wollte, dass es das ganze Konzept für jetzt versteht. (Erkläre ich einen Codeblock, anstatt sie nisten)

HttpRequest.m

- (void) sendRequestFromURL: (NSString *) url 
       completion:(void (^)(NSString *, NSError *))completionBlock { 

    NSURL *myURL = [NSURL URLWithString: url]; 
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL]; 
    NSURLSession *session = [NSURLSession sharedSession]; 


    NSURLSessionDataTask *task = [session dataTaskWithRequest: request 
              completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 

               //Create a block to handle the background thread in the dispatch method. 
               void (^runAfterCompletion)(void) = ^void (void) { 
                if (error) { 
                 completionBlock (nil, error); 
                } else { 
                 NSString *dataText = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]; 
                 completionBlock(dataText, error); 
                } 
               }; 

               //Dispatch the queue 
               dispatch_async(dispatch_get_main_queue(), runAfterCompletion); 
              }]; 
    [task resume]; 

} 

ViewController.m

- (IBAction)buttonSearch:(id)sender { 

    NSString *const myURL = @"https://en.wiktionary.org/wiki/incredible"; 

    HttpRequest *http = [[HttpRequest alloc] init]; 

    [http sendRequestFromURL: myURL 
        completion: ^(NSString *str, NSError *error) { 
         if (error) { 
          self.outputText.text = [error localizedDescription]; 
         } else { 
          self.outputText.text = str; 
         } 
        }]; 
} 

Bitte zögern Sie nicht auf meinem neuen Code zu kommentieren. Stil, falsche Verwendung, falscher Fluss; Feedback ist sehr wichtig in dieser Phase des Lernens, so kann ich ein besserer Entwickler werden :)

Nochmals vielen Dank für die Antworten.

+0

Sie sollten einen Completion-Handler-Block die gleiche verwenden Sie eine in der "DataTaskWithRequest" -Aufruf. Fügen Sie den Parametern von 'sendRequestFromURL' einen Block als Abschlussblock hinzu, und rufen Sie diesen Block auf, wenn die Anfrage beendet wurde. In den Block schreiben Sie Ihren Code für die Aktualisierung der Bezeichnung – luk2302

Antwort

0

Ihre sendRequestFromURL Funktion Rewrite einen Abschluss Block zu nehmen:

- (void) sendRequestFromURL: (NSString *) url 
    completion: (void (^)(void)) completion 
{ 
    NSURL *myURL = [NSURL URLWithString: url]; 
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL]; 
    NSURLSession *session = [NSURLSession sharedSession]; 


    NSURLSessionDataTask *task = [session dataTaskWithRequest: request 
     completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) 
     { 
     self.responseText = [[NSString alloc] initWithData: data 
      encoding: NSUTF8StringEncoding]; 
     if (completion != nil) 
     { 
      //The data task's completion block runs on a background thread 
      //by default, so invoke the completion handler on the main thread 
      //for safety 
      dispatch_async(dispatch_get_main_queue(), completion); 
     } 
     }]; 
     [task resume]; 
} 

Dann, wenn Sie sendRequestFromURL nennen, Pass in dem Code, den Sie ausführen möchten, wenn die Anforderung als Abschlussblock bereit ist:

[self.sendRequestFromURL: @"http://www.someURL.com&blahblahblah", 
    completion:^
    { 
    //The code that you want to run when the data task is complete, using 
    //self.responseText 
    }]; 

    //Do NOT expect the result to be ready here. It won't be. 

Der obige Code verwendet ein Completi auf Block ohne Parameter, weil der Code den Antworttext in einer Instanzvariable gespeichert hat. Es wäre typischer, die Antwortdaten und den NSError als Parameter an den Vervollständigungsblock zu übergeben. Siehe @ Yahohos Antwort für eine Version von sendRequestFromURL, die einen Completion-Block mit einer Ergebniszeichenfolge und einem NSError-Parameter akzeptiert.

(Hinweis: Ich habe den Code oben im SO Post Editor geschrieben. Er hat wahrscheinlich ein paar Syntaxfehler, aber es ist als Leitfaden gedacht, nicht Code, den Sie kopieren und einfügen können. Objective-C Block Syntax ist irgendwie böse und ich bekomme es in der Regel das erste Mal mindestens die Hälfte der Zeit falsch.)

+0

Vielen Dank für die Antwort Duncan. Das hat mir sehr geholfen. Ich habe den Code unter Verwendung des Feedbacks aktualisiert, fühle mich frei, einen Blick zu werfen und einen Anfängerfehler zu entdecken :) –

1

Sie wissen was, verwenden Sie AFNetworking, um Ihr Leben zu retten.

Oder einfach Ihre Httprequest des sendRequestFromURL ändern:

- (void)sendRequestFromURL:(NSString *)url completion:(void(^)(NSString *str, NSError *error))completionBlock { 

NSURL *myURL = [NSURL URLWithString: url]; 
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL]; 
NSURLSession *session = [NSURLSession sharedSession]; 


NSURLSessionDataTask *task = [session dataTaskWithRequest: request 
             completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 
              dispatch_async(dispatch_get_main_queue(), ^{ 
               if (error) { 
                completionBlock(nil, error); 
               } else { 
                completionBlock([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], error); 
               } 
              }); 
             }]; 
[task resume]; 
} 

und rufen wie diese

[http sendRequestFromURL:@"https://en.wiktionary.org/wiki/incredible" completion:^(NSString *str, NSError *error) { 
    if (!error) { 
     self.outputLabel.text = str; 
    } 
}]; 
+0

Ihre und meine Antworten sind sehr ähnlich. Ich würde jedoch vorschlagen, den Completion-Block im Hauptthread aufzurufen, da die Completion-Handler von NSURLSessionDataTask standardmäßig in einem Hintergrundthread aufgerufen werden und es leicht ist, die UI direkt im Completion-Block zu vergessen und zu versuchen. –

+0

Dank erinnern, aber ich habe die completionBlock auf Hauptthread, 'dispatch_async (dispatch_get_main_queue() aufrufen,^{ if (Fehler) { completionBlock (nil, Fehler); } else { completionBlock ([[NSString alloc] initWithData: Datencodierung: NSUTF8StringEncoding], Fehler); } }); ' – Yahoho

+0

Sie taten also. (Voted.) Ich bin mir nicht sicher, wie ich das vermisst habe. –

0

Wenn Sie dann einfach Art und Weise wollen Sie nicht separate Klasse für Call webservice machen. Mach einfach meadhod in viewController.m statt.Ich schreibe meinen sendRequestFromURL in Ihrem viewController.m und aktualisiert Text in Abschluss-Handler etwas Ihres Label ist wie,

- (void) sendRequestFromURL: (NSString *) url { 

NSURL *myURL = [NSURL URLWithString: url]; 
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL]; 
NSURLSession *session = [NSURLSession sharedSession]; 


NSURLSessionDataTask *task = [session dataTaskWithRequest: request 
             completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 

              dispatch_async(dispatch_get_main_queue(), ^{ 

               self.responseText = [[NSString alloc] initWithData: data 
                          encoding: NSUTF8StringEncoding]; 

               self.outputLabel.text = self.responseText; 
              }) 

             }]; 
[task resume]; 

}