2015-12-04 1 views
0

Quelqu'un peut-il expliquer pourquoi l'achèvement retourne tableau vide?retour de la fonction trop tôt

La fonction:

import Foundation 

class IMBD{ 
    func searchMovies(searchText:String, completion: (result: [Movies]) -> Void){ 

     var movies = [Movies]() 

     let replacedMovieTitle = searchText.stringByReplacingOccurrencesOfString(" ", withString: "+") 
     let URLString = "http://www.omdbapi.com/?s=\(replacedMovieTitle)&y=&r=json" 

     let URL = NSURL(string: URLString) 
     let session = NSURLSession.sharedSession() 
      let task = session.dataTaskWithURL(URL!, completionHandler: {(data, response, error) -> Void in 
      do{ 

       let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSDictionary 
       if let search = jsonData["Search"] as? [[String : AnyObject]]{ 
        for hit in search{ 
         guard let title = hit["Title"] as? String else{ 
           print("returna title") 
          return 

         } 
         guard let year = hit["Year"] as? String else{ 
          print("returna year") 
          return 
         } 
         guard let imbdID = hit["imdbID"] as? String else{ 
          print("returna imbd") 
          return 
         } 
         guard let poster = hit["Poster"] as? String else{ 
          print("returna poster") 
          return 
         } 
         let movie = Movies(title: title, released: year, poster: poster, imbdID: imbdID) 
          movies.append(movie) 

        } 
       } 

      }catch{ 
      } 

     }).resume() 
     completion(result: movies) 

    } 
} 

L'appel:

imbd.searchMovies(searchtext!, completion: { (result) -> Void in 
    self.movieList = result 
}) 

Répondre

3

Vous devez appeler vos poignées achèvement intérieur la fermeture dataTaskWithURL, et non après. Cela s'exécute de manière asynchrone, donc si vous appelez votre completion en dehors de la fermeture, il sera appelé avant que la requête asynchrone n'ait eu l'occasion de récupérer quoi que ce soit. De plus, rappelez-vous que cette fermeture ne s'exécute pas sur le thread principal, vous devez donc également l'envoyer à la file d'attente principale (à partir de dataTaskWithURL).


Par exemple:

class IMDB { 
    func searchMovies(searchText:String, completion: (result: [Movie]?, error: NSError?) -> Void) -> NSURLSessionTask { 
     var movies = [Movie]() 

     let allowedCharacters = NSCharacterSet.alphanumericCharacterSet().mutableCopy() as! NSMutableCharacterSet 
     allowedCharacters.addCharactersInString("-._* ") 
     let replacedMovieTitle = searchText.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacters)! 
      .stringByReplacingOccurrencesOfString(" ", withString: "+") 
     let URLString = "http://www.omdbapi.com/?s=\(replacedMovieTitle)&y=&r=json" 

     let URL = NSURL(string: URLString) 
     let session = NSURLSession.sharedSession() 
     let task = session.dataTaskWithURL(URL!) { data, response, error in 
      guard error == nil && data != nil else { 
       dispatch_async(dispatch_get_main_queue()) { 
        completion(result: nil, error: error) 
       } 
       return 
      } 

      do { 
       let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as! NSDictionary 
       if let search = jsonData["Search"] as? [[String : AnyObject]]{ 
        for hit in search{ 
         guard let title = hit["Title"] as? String else{ 
          print("returna title") 
          continue 
         } 
         guard let year = hit["Year"] as? String else{ 
          print("returna year") 
          continue 
         } 
         guard let imdbID = hit["imdbID"] as? String else{ 
          print("returna imbd") 
          continue 
         } 
         guard let poster = hit["Poster"] as? String else{ 
          print("returna poster") 
          continue 
         } 
         let movie = Movie(title: title, released: year, poster: poster, imdbID: imdbID) 
         movies.append(movie) 
        } 
       } 
       dispatch_async(dispatch_get_main_queue()) { 
        completion(result: movies, error: nil) 
       } 
      } catch let error as NSError { 
       dispatch_async(dispatch_get_main_queue()) { 
        completion(result: nil, error: error) 
       } 
      } 
     } 
     task.resume() 

     return task 
    } 
} 

Un couple d'autres changements dans l'extrait de code ci-dessus comprennent:

  1. Ajouter guard en cas il y avait une erreur de réseau fondamental (par exemple serveur distant vers le bas, pas d'accès Internet, etc.)

  2. Dans le guard les instructions qui vérifient les valeurs nil, plutôt que d'effectuer un return (auquel cas aucun autre résultat ne sera recueilli), vous pouvez vouloir simplement continue (c'est-à-dire passez à l'enregistrement suivant). Vous voyez généralement guard en conjonction avec return, mais dans ce cas, continue est probablement plus approprié. Franchement, vous voudrez peut-être aller plus loin et considérer si certaines d'entre elles pourraient être optionnelles, plutôt que de rejeter tout l'enregistrement. Notamment, poster me frappe comme quelque chose qui pourrait être nil s'il n'y avait pas d'affiche disponible. Peut-être que certains des autres devraient aussi être optionnels (par exemple si un film n'a pas encore été publié, peut-être n'a-t-il pas de date de sortie?).

  3. Les occurrences de "imbd" ont été remplacées par "imdb".

  4. La classe Movies a été renommée Movie (puisque chaque instance est un film unique, pas une collection d'entre eux). J'ai changé le bloc completion pour rendre [Movie] facultatif et pour retourner le . Sans cela, vous n'avez pas le moyen de faire la différence entre "ne pouvait pas trouver un titre de ce nom" et "whoops, quelque chose s'est mal passé".

  5. Lorsque nous appelons la fermeture de completion dans le dataTaskWithURL, il peut être très utile d'avoir searchMovies expédition completion rappelle à la file d'attente principale, comme ci-dessus. En effet, les mises à jour de l'interface utilisateur doivent toujours se produire sur le thread principal et, fréquemment, lorsque vous écrivez des routines de ce type, vous pouvez mettre à jour l'interface utilisateur ou le modèle avec les résultats.

    Ce n'est pas toujours nécessaire de le faire comme ceci (vous pouvez simplement avoir cet appel completion directement à partir du thread d'arrière-plan et laisser la routine searchMovies envoyer manuellement des trucs au thread principal lui-même), mais je trouve souvent Il est utile d'avoir cette méthode de recherche juste envoyer le completion retour au thread principal et être fait avec.

  6. En pratique, je renvoie toujours le NSURLSessionTask lors de l'exécution de demandes. Vous n'en aurez peut-être pas besoin maintenant, mais à une date ultérieure, vous voudrez peut-être annuler une demande en cours et avoir une référence à la tâche peut être utile. Cela ne fait pas de mal de le retourner, et cela peut être utile.

  7. Vous devriez probablement échapper en pourcentage aux valeurs que vous ajoutez à l'URL. Notamment la présence de caractères & ou + pourrait être problématique. Notez, dans ce cas, il semble que ce site ne le gère pas correctement, mais il est bon de prendre l'habitude d'échapper correctement les valeurs dans une requête.

    Personnellement, je conserve cette logique d'échappement en pourcentage dans une extension String, mais je voulais garder cela simple, donc je l'ai intégré directement dans cette méthode, mais j'espère que cela illustre l'idée.

+0

Merci! Cela a fonctionné :) Où devrais-je mettre l'expédition ?? –

+0

@ AsleBenjaminKinnerød - Voir l'extrait de code dans la réponse révisée. – Rob

+0

Le point 6 est discutable: la décision d'exécuter le code à l'intérieur du completionhandler doit être prise par l'appelant, le destinataire ne sait pas si le code dans le completionblock doit être appelé sur un thread spécifique. Il se pourrait que les résultats ne soient analysés, aucune interface utilisateur impliquée. Dans ce cas, pas besoin d'envoyer à la file d'attente principale du tout. En général, les utilisateurs d'API impliquant des gestionnaires d'achèvement doivent supposer que l'achèvement exécute un thread arbitraire. L'expédition devrait être à l'intérieur du bloc. – Joride