Révision du 2017.01.04 – conversion vers Xcode 8.0 et utilisation de Alamofire
Dans le tutoriel TIMFlix ou ‘Les Amis de la science« , nous avons construit une application qui affichait une liste de vidéos à l’intérieur d’un UITableView.
Les informations des vidéos, ainsi que l’image des affiches, étaient renseignées localement grace à un fichier de propriétés.
L’inconvénient de cette approche est évident lorsque vient le temps d’y ajouter de nouvelles vidéos.
Dans ce tutoriel, nous verrons comment utiliser une API RESTFull pour fournir des données à une application Xcode.
Pré-requis:
Avoir complété le tutoriel TIMFlix ou bien le laboratoire ‘Les amis de la science‘, ou bien avoir une maitrise de l’objet UITableView + segue + passage de paramètres entre les scènes d’une app.
Vidéo du résultat final:
C’est une pratique courante que d’offrir l’accès à des données via des URLs web selon la méthode RESTful.
L’avantage de cette approche est qu’il n’est pas nécessaire d’offrir un accès direct au SGBD (système de gestion de la base de données) de l’organisation.
Par exemple, via une connexion à la BD par programmation, par utilisation d’outils comme phpMyAdmin, Oracle connect ou, des connaissances techniques du SGBD.
La technique, est d’offrir des scripts de type ‘serveur web’ de traitements et d’accès à la base de données.
Ces scripts, écrits en PHP, ASP, Python, …, peuvent alors être lancés à partir d’une simple requête HTTP soit dans un fureteur ou soit dans une application: en utilisant des fonctions ou de objets tel que curl(), NSArray(URL:) et autres.
Voir la définition de Representational State Transfer.
Pour l’interrogation des données, le script peut retourner les données dans un format indépendant du SGBD tel que;
Texte, XML, JSON, CSV et PList.
Par exemple, l’URL suivante,
http://uneApi.org/obtenir_liste_stages.php?region=mtl&format=csv
Pourrait retourner la liste des lieux de stage pour la région de Montréal en format csv:
« Ubisoft », »2″, »Hiv17″
« Gameloft », »1″, »Hiv17″
Pour programmer une API Web d’accès à une base de données il suffit d’avoir accès à un serveur Web (IIS, Apache, …), à un langage script coté serveur (php, ASP, node.JS, …) et à un SGBD (MySQL, MSSQL, Oracle, PostgreSQL, … ).
Dans le cadre d’apprentissages, une solution comme WAMP est idéale.
En pratique, voici comment offrir un API Web d’interrogation d’une table proposant des pensées du jour:
API.TIM.01 – À partir d’une table, ‘penseesdujours’:
API.TIM.02 – D’un script PHP à l’adresse ‘/unScript.php’
// Requête SQL pour obtenir les enregistrements de la table penseesdujours // Connexion à la BD mysql_connect("localhost", "user", "password") or die(mysql_error()); mysql_select_db("cours_xcode") or die(mysql_error()); // Exécuter un requête SQL $res = mysql_query("SELECT `nom_ajout` , `created_at` , `adresse_ip` , `pensee_texte`, `pensee_auteur`, `pensee_lien_image` FROM penseesdujours") or die(mysql_error()); // Parcourir les éléments du tableau de résultats while($r = mysql_fetch_assoc($res)) { $xx = array_map("utf8_encode", $r); foreach ($xx as $key => $value) { echo "clé = $key, valeur = $value\n"; } // foreach } // while encore un résultat
API.TIM.03 – L’API retournera le résultat texte suivant:
clef = nom_ajout, valeur = Alain
clef = created_at, valeur = 2014-11-01 05:11:43
clef = categorie, valeur = 3
clef = adresse_ip, valeur = 24.200.185.163
clef = pensee_texte, valeur = Il etait une fois un gars …
clef = pensee_auteur, valeur = Moi
clef = pensee_lien_image, valeur = http://www.imagesdoc.com/var/bayard/storage/images/smk/images-doc/images/images-doc-plus/que-vois-tu/photo-mystere-2-image-3/24513202-1-fre-FR/Photo-mystere-2-Image-3.jpg
lien de test
API.TIM.04 – Voici un script PHP qui retourne le résultat en format ‘plist’:
echo '<?xml version="1.0" encoding="UTF-8"?>'."\n"; echo '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'."\n"; echo '<plist version="1.0">'."\n"; echo "<!-- Liste générée par l'API TIM le ".date('Y-m-d h:m:s')." -->\n"; echo "<!-- (c) 2014-2016 par Alain Boudreault -->\n"; echo "<array>\n"; while($r = mysql_fetch_assoc($res)) { $xx = array_map("utf8_encode", $r); echo "\t<dict>\n"; foreach ($xx as $key => $value) { echo "\t\t<key>".$key."</key>\n\t\t<string>".$value."</string>\n"; } // foreach echo "\t</dict>\n"; } // while echo "</array>\n</plist>";
Note: Voir la structure d’un fichier ‘plist’ sous Xcode:
API.TIM.05 – Produira le résultat suivant:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <!-- Liste générée par l'API TIM le 2016-11-02 09:11:55 --> <!-- (c) 2014-2016 par Alain Boudreault --> <array> <dict> <key>nom_ajout</key> <string>Alain</string> <key>created_at</key> <string>2014-11-01 06:11:47</string> <key>adresse_ip</key> <string>24.200.185.163</string> <key>pensee_texte</key> <string>On ne reçoit pas la sagesse, il faut la découvrir soi-même, après un trajet que personne ne peut faire pour nous, ne peut nous épargner.</string> <key>pensee_auteur</key> <string>Marcel Proust</string> <key>pensee_lien_image</key> <string>http://upload.wikimedia.org/wikipedia/commons/thumb/7/7e/Marcel_Proust_1900.jpg/220px-Marcel_Proust_1900.jpg</string> </dict> </array> </plist>
lien de test
API.TIM.06 – Un script PHP de mise en format ‘JSON‘
// tableau du résultat final $resultat = array(); $resultat['info'] = array("API_TIM" => "version 2016.10.01", "type_requete" => Input::get('mode')); while($r = mysql_fetch_assoc($res)) { // Encoder les caractères accentués. $tableauAvecAccents[] = array_map("utf8_encode", $r); } // while $resultat['resultat'] = $tableauAvecAccents; // Convertir un tableau en format JSON echo json_encode($resultat);
API.TIM.07 – Produira le résultat suivant:
{ "info":{"API_TIM":"version 2016.10.01","type_requete":"rnd"}, "resultat":[ { "nom_ajout":"admin", "created_at":"2014-11-01 10:11:56", "adresse_ip":"24.200.185.163", "pensee_texte":"La qualit\u00e9 d'un homme se calcule \u00e0 sa d\u00e9mesure ; tentez, essayez, \u00e9chouez m\u00eame, ce sera votre r\u00e9ussite\u0085", "pensee_auteur":"Jacques Brel", "pensee_lien_image":"https:\/\/encrypted-tbn2.gstatic.com\/images?q=tbn:ANd9GcQqWOL6fBMrhIECWoOuY3aEdxrm6biaZTdi5Kp72TC_xG9L11GS" }, ... ] }
lien de test
Exemple php complet:
<?php // Fichier: apitim.php // Par: Alain Boudreault // Date: 2016.10.23 // Description: // ------------------------------------------------------------- $servername = "localhost"; $username = "votreAcces"; $password = "votreMotDePasse"; $dbname = "votreBD"; $mode = isset($_GET["mode"]) ? $_GET["mode"] : "all"; $quant = isset($_GET["quant"]) ? $_GET["quant"] : "2"; $format = isset($_GET["format"]) ? $_GET["format"] : "texte"; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT `nom_ajout` , `created_at`, `categorie`, `adresse_ip` , `pensee_texte`, `pensee_auteur`, `pensee_lien_image` FROM penseesdujours"; if ($mode == "all") { $sql = "SELECT `nom_ajout` , `created_at`, `categorie`, `adresse_ip` , `pensee_texte`, `pensee_auteur`, `pensee_lien_image` FROM penseesdujours"; } // mode=all if ($mode == "rnd") { $sql = "SELECT `nom_ajout` , `created_at`,`categorie`, `adresse_ip` , `pensee_texte`, `pensee_auteur`, `pensee_lien_image` FROM penseesdujours ORDER BY RAND() LIMIT $quant"; } // mode=rnd if ($mode == "adulte") { $sql = "SELECT * FROM (SELECT `categorie`,`nom_ajout`, `created_at`, `adresse_ip`, `pensee_texte`, " . " `pensee_auteur`, `pensee_lien_image` FROM penseesdujours where categorie = 3) as tmp ORDER BY RAND() LIMIT $quant"; echo $sql . "\n\n"; } // mode=adulte $res = $conn->query($sql); // En format plist if ($format == "plist") { echo '<?xml version="1.0" encoding="UTF-8"?>'."\n"; echo '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'."\n"; echo '<plist version="1.0">'."\n"; echo "<!-- Liste générée par l'API TIM le ".date('Y-m-d h:m:s')." -->\n"; echo "<!-- (c) 2014-2016 par Alain Boudreault -->\n"; echo "<array>\n"; while($r = $res->fetch_assoc()) { $xx = array_map("utf8_encode", $r); echo "\t<dict>\n"; foreach ($xx as $key => $value) { echo "\t\t<key>".$key."</key>\n\t\t<string>".$value."</string>\n"; } // foreach echo "\t</dict>\n"; } // while echo "</array>\n</plist>"; } // format = plist // En format texte if ($format == "texte") { // Liste simple while($row = $res->fetch_assoc()) { foreach ($row as $key => $value) { echo "clef = ".$key.", valeur = ".$value."<br>\n"; } // foreach } // while } // if format == texte // En format json if ($format == "json") { // tableau du résultat final $resultat = array(); $resultat['info'] = array("API_TIM" => "version 2016.10.01", "type_requete" => "json"); while($r = $res->fetch_assoc()) { // Encoder les caractères accentués. $tableauAvecAccents[] = array_map("utf8_encode", $r); } // while $resultat['resultat'] = $tableauAvecAccents; // Convertir un tableau en format JSON echo json_encode($resultat, JSON_PRETTY_PRINT); } // if format == json $conn->close(); // echo phpinfo(); ?>
Étant donné le lien suivant:
Obtenir cotes de la bourse
Nous obtenons la structure suivante:
{ "query":{ // Dictionary<String, Any> "count":4, "created":"2016-11-01T15:53:16Z", "lang":"fr-ca", "results": { // Dictionary<String, Any> // *** Tableau des cotes "quote":[ // Array<Dictionary<String, Any>> // **** Premier élément { "symbol":"YHOO", "Ask":"40.89", "AverageDailyVolume":"10498500", "Bid":"40.86", "AskRealtime":null, "BidRealtime":null, "BookValue":"36.39", "Change_PercentChange":"-0.23 - -0.55%", "Change":"-0.23", "Commission":null, "Currency":"USD", "ChangeRealtime":null, "AfterHoursChangeRealtime":null, "DividendShare":null, "LastTradeDate":"10/31/2016", "TradeDate":null, "EarningsShare":"-5.11", "ErrorIndicationreturnedforsymbolchangedinvalid":null, "EPSEstimateCurrentYear":"0.58", "EPSEstimateNextYear":"0.62", "EPSEstimateNextQuarter":"0.14", "DaysLow":null, "DaysHigh":null, "YearLow":"26.15", "YearHigh":"44.92", "HoldingsGainPercent":null, "AnnualizedGain":null, "HoldingsGain":null, "HoldingsGainPercentRealtime":null, "HoldingsGainRealtime":null, "MoreInfo":null, "OrderBookRealtime":null, "MarketCapitalization":"39.78B", "MarketCapRealtime":null, "EBITDA":"90.38M", "ChangeFromYearLow":"15.40", "PercentChangeFromYearLow":"+58.89%", "LastTradeRealtimeWithTime":null, "ChangePercentRealtime":null, "ChangeFromYearHigh":"-3.37", "PercebtChangeFromYearHigh":"-7.50%", "LastTradeWithTime":"4:00pm - <b>41.55</b>", "LastTradePriceOnly":"41.55", "HighLimit":null, "LowLimit":null, "DaysRange":null, "DaysRangeRealtime":null, "FiftydayMovingAverage":"42.83", "TwoHundreddayMovingAverage":"39.65", "ChangeFromTwoHundreddayMovingAverage":"1.90", "PercentChangeFromTwoHundreddayMovingAverage":"+4.78%", "ChangeFromFiftydayMovingAverage":"-1.28", "PercentChangeFromFiftydayMovingAverage":"-2.98%", "Name":"Yahoo! Inc.", "Notes":null, "Open":null, "PreviousClose":"41.78", "PricePaid":null, "ChangeinPercent":"-0.55%", "PriceSales":"8.04", "PriceBook":"1.15", "ExDividendDate":null, "PERatio":null, "DividendPayDate":null, "PERatioRealtime":null, "PEGRatio":"-124.19", "PriceEPSEstimateCurrentYear":"71.64", "PriceEPSEstimateNextYear":"67.02", "Symbol":"YHOO", "SharesOwned":null, "ShortRatio":"4.25", "LastTradeTime":"4:00pm", "TickerTrend":null, "OneyrTargetPrice":"45.14", "Volume":"10108", "HoldingsValue":null, "HoldingsValueRealtime":null, "YearRange":"26.15 - 44.92", "DaysValueChange":null, "DaysValueChangeRealtime":null, "StockExchange":"NMS", "DividendYield":null, "PercentChange":"-0.55%"}, // *** Deuxième élément {"symbol":"AAPL","Ask":"113.60", ... } ] // **** Fin du tableau des cotes } // results } // query }
Action API.TIM.01 – Dans un nouveau projet, ajoutons le code suivant:
// ViewController.swift // Ajouter ceci dans le fichier info.plist /* <key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict> */ import UIKit class ViewController: UIViewController { // let URLYahooFinance = "http://query.yahooapis.com/v1/public/yql?q=select%20%2a%20from%20yahoo.finance.quotes%20where%20symbol%20in%20%28%22YHOO%22%2C%22AAPL%22%2C%22GOOG%22%2C%22MSFT%22%29%0A%09%09&env=http%3A%2F%2Fdatatables.org%2Falltables.env&format=json" // requete = http://query.yahooapis.com/v1/public/yql?q=select * from yahoo.finance.quotes where symbol in ("MSFT","FB","INTC","HPQ","AAPL","AMD","COKE")&env=http:/datatables.org/alltables.env&format=json private var dic_resultats = Dictionary<String, Any>() private var _listeDesItems = Array<Dictionary<String, Any>>() // URL vers l'API finance de Yahoo let YahooFinanceURLpart1 = "http://query.yahooapis.com/v1/public/yql?q=" let requeteSQL = "select * from yahoo.finance.quotes where symbol in (" let porteFeuille = "\"MSFT\",\"FB\",\"INTC\",\"HPQ\",\"AAPL\",\"AMD\",\"COKE\"" let YahooFinanceURLpart2 = ")&env=http://datatables.org/alltables.env&format=json" var URLYahooFinance = "" func obtenirLesDonnées(_ url:String) { let uneURL = URL(string: url)! //Danger! /// Exécuter le traitement suivant en parallèle /// DispatchQueue.main.async ( execute: { if let _données = NSData(contentsOf: uneURL) as? Data { do { let json = try JSONSerialization.jsonObject(with: _données, options: JSONSerialization.ReadingOptions()) as? Dictionary<String, Dictionary<String, Any>> print("Conversion JSON réussie") self.dic_resultats = json! //print(self.dic_resultats) // Créer un tableau à partir du champ 'resultats' if let listeDesItems = ((self.dic_resultats["query"] as? Dictionary<String, Any>)?["results"]as? Dictionary<String, Any>)?["quote"] as? Array<Dictionary<String, Any>> { self._listeDesItems = listeDesItems print("Liste des items:\n\(self._listeDesItems)") // self.collectionDesItems.reloadData() } // print(json) } catch { print("\n\n#Erreur: Problème de conversion json:\(error)\n\n") } // do/try/catch } else { print("\n\n#Erreur: impossible de lire les données via:\(self.URLYahooFinance)\n\n") } // if let _données = NSData /// }) // DispatchQueue.main.async } // obtenirLesDonnées(_ url:String) override func viewDidLoad() { super.viewDidLoad() // Construire l'URL de l'API Yahoo à partir du portefeuille local URLYahooFinance = YahooFinanceURLpart1 + (requeteSQL + porteFeuille).addingPercentEncoding(withAllowedCharacters: CharacterSet.alphanumerics)! + YahooFinanceURLpart2 print(URLYahooFinance) obtenirLesDonnées(URLYahooFinance) } // viewDidLoad() }
Analyse du code précédent:
Action: Testons l’application
Résultat obtenu:
2016-11-05 09:40:54.698663 Tester Yahoo finance[8399:131637] [] tcp_connection_get_statistics DNS: 3ms/9ms since start, TCP: 88ms/103ms since start, TLS: 0ms/0ms since start Conversion JSON réussie Liste des items: [["Change_PercentChange": -0.50 - -0.84%, "EPSEstimateCurrentYear": 2.97, "LastTradePriceOnly": 58.71, "ChangePercentRealtime": <null>, "Open": 58.65, "HoldingsGainRealtime": <null>, "DaysLow": 58.52, "PERatio": 28.12, "PERatioRealtime": <null>, "OneyrTargetPrice": 63.81, "FiftydayMovingAverage": 58.21, "AskRealtime": <null>, "SharesOwned": <null>, "DaysHigh": 59.28, "Name": Microsoft Corporation, "Change": -0.50, "DividendYield": 2.62, "YearHigh": 61.37, "DividendPayDate": 12/8/2016, "YearRange": 48.04 - 61.37, ...
func afficherActions() { print("---------------------------------------") for action in _listeDesItems{ if let _nom = action["Name"], let _prix = action["Ask"] { print("Action: \(_nom), prix: \(_prix)") } } // for action in print("---------------------------------------") } // afficherActions()
Résultat obtenu:
--------------------------------------- Action: Microsoft Corporation, prix: 58.74 Action: Facebook, Inc., prix: 120.72 Action: Intel Corporation, prix: 34.12 Action: HP Inc. Common Stock, prix: 14.71 Action: Apple Inc., prix: 108.85 Action: Advanced Micro Devices, Inc., prix: 6.57 Action: Coca-Cola Bottling Co. Consolid, prix: <null> ---------------------------------------
Et pour une lecture des données à chaque n sec:
func viewDidLoad() { ... Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(self.doTimer), userInfo: nil, repeats: true) } // viewDidLoad() func doTimer(){ obtenirLesDonnées(URLYahooFinance) afficherActions() /// Actualiser une collectionView /// tableViewActions.reloadData() }
// Retirer le commentaire des lignes suivantes: DispatchQueue.main.async ( execute: { ... }) // DispatchQueue.main.async
Analysons le résultat suivant:
http://query.yahooapis.com/v1/public/yql?q=select%20%2A%20from%20yahoo%2Efinance%2Equotes%20where%20symbol%20in%20%28%22MSFT%22%2C%22FB%22%2C%22INTC%22%2C%22HPQ%22%2C%22AAPL%22%2C%22AMD%22%2C%22COKE%22)&env=http://datatables.org/alltables.env&format=json --------------------------------------- --------------------------------------- 2016-11-05 10:23:11.400069 Tester Yahoo finance[11698:169904] subsystem: com.apple.BackBoardServices.fence, ca
La liste est vide la première fois.
Raison: Nous affichons le contenu du tableau avant d’avoir reçu la réponse de l’API.
Précédemment, l’exécution du programme bloquait à la lecture des données à partir d’Internet.
Maintenant, avec ‘ DispatchQueue.main.async ( execute: ‘, la lecture des données est faite en arrière plan et notre programme principal continu les traitements.
obtenirLesDonnées(URLYahooFinance) // Envoyé en arrière plan afficherActions() // *** N'attend plus après l'instruction précédente.
Avec cette technique, il faut traiter l’affichage des données et l’actualisation des composants MVC à l’intérieur du bloc de code (inline function, fonction anonyme).
Dans ce cas, les variables doivent-être précédées de ‘self’; self.monCollectionView.reloadData().
Voir le code de la solution:
Télécharger la solution
Obtenir la valeur des monnaies en fonction de USD à partir de l’API suivante:
http://finance.yahoo.com/webservice/v1/symbols/allcurrencies/quote?format=json
Action: Écrire une application qui affiche, dans un UITableView, le Code (symbol) des devises ainsi que le prix (price). Actualiser les données à chaque 15 secondes.
Obtenir la température à partir de l’API suivante:
Température de 10 villes
Action: Écrire une application qui affiche, dans un UITableView, le nom de la ville ainsi que la température actuelle. Actualiser les données à chaque 15 secondes.
Le site ‘TIM.Ze.Game’ propose une API d’accès aux données qu’il compile. Ces données nous renseignent sur des pochettes de jeux, les critiques, les suiveux, …
Pour interroger l’api, il suffit de suivre l’URL suivante:
http://api-tim.ze.game?apikey=VOTRECLE&q=MODELE_DE_RECHERCHE&quant=NB_PAGES&format=json
Comme par exemple: http://prof-tim.cstj.qc.ca/cours/xcode/sources/timgames/api.timgames.php?apikey=ceciEstUltraSecret&q=Star&quant=50&format=json
La requête précédente devrait nous retourner une structure JSON contenant une liste de 50 pochettes dont la chaîne ‘Star’ apparaît dans le titre du jeu.
Pour obtenir, à partir du réseau Internet, des données en format JSON et créer un dictionnaire, il suffira d’utiliser la technique présentée plus haut:
if let NSData(contentsOf: uneURL) as? Data {
do {
let json = try JSONSerialization.jsonObject(with: _données, options: JSONSerialization.ReadingOptions()) as? Dictionary<String, Any>
}
Action 1.1 – Ouvrons le projet Projet – TIM.Ze.Game – depart et exécutons l’application
Action 1.1.2 – Examinons le code de la classe ‘Intro’; animations et trame sonore:
// Voici comment animer une propriété: titre1.alpha = 0 UIView.animate(withDuration: 3.0, delay: 0.0, options: UIViewAnimationOptions.curveEaseInOut, animations: { self.titre1.alpha = 1.0 }, completion: nil) // Voici comment jouer un fichier audio: var player: AVAudioPlayer? func jouerIntro() { let url = Bundle.main.url(forResource: "fichier", withExtension: "mp3")! do { player = try AVAudioPlayer(contentsOf: url) guard let player = player else { return } // autre façon de faire un if let _ {} else {} player.prepareToPlay() player.play() } catch let error { print(error) } // do/try/catch } // func jouerIntro()
Action 1.1.3 – Ajoutons le code suivant à la méthode « obtenirDonnéesVersionBloquante » de la classe contrôleur de la scène principale.
// *** Code à insérer dans la méthode "obtenirDonnéesVersionBloquante" let strURL = "http://prof-tim.cstj.qc.ca/cours/xcode/sources/timgames/api.timgames.php?apikey=\(Globales.CLE_API)&q=\(chaine)&format=json&quant=\(tailleRequete.text!)" let uneURL = URL(string: strURL)! //Danger! /// Exécuter le traitement suivant en parallèle /// DispatchQueue.main.async ( execute: { if let _données = NSData(contentsOf: uneURL) as? Data { do { let json = try JSONSerialization.jsonObject(with: _données, options: JSONSerialization.ReadingOptions()) as? Dictionary<String, Any> print("Conversion JSON réussie") self._résultatDeLaRequête = json! // Créer un tableau à partir du champ 'resultats' if let listeDesItems = self._résultatDeLaRequête["resultats"] as? Array<Dictionary<String, Any>> { self._listeDesItems = listeDesItems print("Liste des items:\n\(self._listeDesItems)") self.collectionDesItems.reloadData() } // print(json) } catch { print("\n\n#Erreur: Problème de conversion json:\(error)\n\n") } // do/try/catch } else { print("\n\n#Erreur: impossible de lire les données via:\(strURL)\n\n") } // if let _données = NSData /// }) // DispatchQueue.main.async
Action 1.2 – Testons l’application. Remarquez que la méthode ‘obtenirDonnéesVersionBloquante‘ est appelée dans ‘viewDidLoad’.
Nous devrions obtenir le résultat suivant dans la zone ‘debug » d’Xcode:
{ "info": { "api.TIMGames": "version 2016.10.01", "Auteur_API": "Alain Boudreault, AKA Puyansude, AKA ve2cuy", "type_requete": "json", "recherche": "je vais ", "apikey": "ceciEstUltraSecret", "droit_auteur": "Cette API est \u00e0 l'usage exclusif des \u00e9tudiantes et \u00e9tudiants de 'Production Multim\u00e9dia sur Support' de tim.cstj.qc.ca'", "site_web": "http:\/\/prof-tim.cstj.qc.ca\/cours\/xcode\/wp\/index.php\/contenu\/", "nombre": "2", "addresse_IP": "192.226.187.50", "date": { "seconds": 49, "minutes": 43, "hours": 14, "mday": 18, "wday": 2, "mon": 10, "year": 2016, "yday": 291, "weekday": "Tuesday", "month": "October", "0": 1476816229 } }, "resultats": [ { "titre": "Dqprzfuwkkf", "editeur": "TIMGames", "annee": 2012, "rang_semaine": 83, "pochettes": { "petite": "21p.jpg", "grande": "21.jpg" }, "description": "Debwusy rcexuwj ", "like_facebook": 1595, "like_twitter": 6386, "cotes": { "critiques": 10, "joueurs": 9 }, "suiveux": [ { "nom": "Gthqynsuh", "avatar": "avatar-16.jpg" }, { "nom": "Lsicwo q", "avatar": "avatar-27.jpg" } ] }, { "titre": "Kkroahfanaxzlcff", "editeur": "TIMGames", "annee": 1989, "rang_semaine": 80, "pochettes": { "petite": "4p.jpg", "grande": "4.jpg" }, "description": "Debwusy rcexuwj ", "like_facebook": 5660, "like_twitter": 6975, "cotes": { "critiques": 2, "joueurs": 1 }, "suiveux": [ { "nom": "Kwdzpygar", "avatar": "avatar-25.jpg" }, { "nom": "Qeoeffp", "avatar": "avatar-17.jpg" } ] } ] }
NOTE: En analysant la structure précédente, il est possible d’énoncer les axiomes suivants:
Action 1.3 – Affichons le titre des items reçus (à la fin de la méthode: ‘obtenirDonnéesVersionBloquante’)
// Afficher le titre des items for (indice,item) in _listeDesItems.enumerated() { if let _titreItem = item["titre"] as? String { print("Titre item \(indice): \(_titreItem)") } }
Afficher le nom des suiveux sous le titre des items
----------------------------------- Titre: the Rwdqxmoqfuw ----------------------------------- Titre: the Wgy lfypzvqiiieixtw suiveux: Hlslb ----------------------------------- Titre: the Qzrgfldbscceibuhxk suiveux: Rguwqsqm b suiveux: Emdvmv
Remarquer qu’il y a une zone de recherche en haut à droite de l’application. Nous allons utiliser la méthode de délégation ‘textFieldShouldReturn‘ du ‘UITextField’ pour relancer la requête vers l’api TIM.Ze.Game.
Action 2.1 – Effaçons les commentaires du code de la méthode ‘textFieldShouldReturn‘ de la classe contrôleur de la scène principale.
func textFieldShouldReturn(_ textField: UITextField) -> Bool { // print("textFieldShouldReturn") /* ********************************* À compléter .... ********************************* */ // TODO: Convertir la chaine en format 'escaped' pour le web. Par exemple, ' '= %20 let escapedText = textField.text!.addingPercentEncoding(withAllowedCharacters: CharacterSet.alphanumerics)! print("escapedText = \(escapedText)") // TODO: Relancer la requete vers l'API _ = obtenirDonnéesVersionBloquante(escapedText) //---------------------------------- textField.resignFirstResponder() progression.stopAnimating() return true } // textFieldShouldReturn
Action 2.2 – Testons l’application
Résultat: La chaine de saisie de recherche a été encodé en caractères d’échappement WEB.
Nous savons qu’il est possible de créer un objet de type ‘UIImage’ à partir du nom d’un fichier livré avec l’application de la façon suivante:
UIImage(Named: »pochette.jpg »)
Mais lorsque que ce fichier est stocké dans le réseau Internet il faut procéder ainsi:
do {
let _data = try Data(contentsOf: _url, options: Data.ReadingOptions.alwaysMapped)
cellule.pochetteImage.image = UIImage(data: _data)
}
catch { }
Note: La qualité de l’expérience utilisateur va être affectée par le chargement d’un grand nombre d’images. Plus tard, nous proposerons une solution à ce problème.
Action 3.1 – Remplaçons la méthode ‘cellForItem…’ du fichier ViewController.swift » par:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cellule = collectionView.dequeueReusableCell(withReuseIdentifier: "modeleCellule1", for: indexPath) as! CollectionViewCellPerso1 // TODO: 5a - Renseigner les éléments d'interface de la cellule courante: titre, image, ... let itemCourant = _listeDesItems[indexPath.row] let nomFichierPetitePochette = (itemCourant["pochettes"] as? Dictionary<String, String>)?["petite"] let URLFichierImage = "\(Globales.URLDonnées)\(nomFichierPetitePochette!)" print(URLFichierImage) cellule.pochetteTitre.text = itemCourant["titre"] as? String // Version très bloquante: if let _url = URL(string: URLFichierImage) { do { let _data = try Data(contentsOf: _url, options: Data.ReadingOptions.alwaysMapped) cellule.pochetteImage.image = UIImage(data: _data) } catch { print("Ligne \(#line), \(error), ### Exception: Problème avec URL: \(URLFichierImage)") cellule.pochetteImage.image = UIImage(named:Globales.NA_IMAGE) } } // if let _url // TODO: 5b - Renseigner les éléments d'interface en version non bloquante // Renseigner la couleur de l'entête indexPath.row modulo 2 let couleur1 = UIColor.init(red: 140 / 255.0, green: 188 / 255.0, blue: 220 / 255.0, alpha: 1) let couleur2 = UIColor.init(red: 108 / 255.0, green: 145 / 255.0, blue: 168 / 255.0, alpha: 1) let couleur = indexPath.row % 2 == 0 ? couleur1 : couleur2 cellule.entete.backgroundColor = couleur return cellule } // collectionView: cellForItemAt
Action 3.2 – Corrigeons la valeur de retour de la méthode ‘numberOfItems…’
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return _listeDesItems.count } // numberOfItemsInSection
Action 3.3 – Ajoutons le code suivant à la méthode ‘prepareForSegue’
// MARK: Préparer les données pour le segue vers la scène Détails: override func prepare(for segue: UIStoryboardSegue, sender: Any?) { let destination = segue.destination as! ViewControllerDetails destination.detailsItemCourant = _listeDesItems[(collectionDesItems.indexPath(for: sender as! UICollectionViewCell)?.row)!] }
Action 3.4 – Testez l’application. Vous allez remarquer un important manque de réponse de l’application.
Dans la scène « Détails », renseignez la grande image de la pochette et la liste des suiveux (nom et image).
Présentement, les requêtes web de l’application sont bloquantes, C-A-D que l’application attend la réponse de la requête avant de passer à la ligne de code suivante. Cela a pour effet de bloquer aussi l’interactivité de l’application.
Action 4.1 – Modifions la méthode ‘obtenirDonnéesVersionBloquante’ pour rendre la requête non bloquante
/// Exécuter le traitement suivant en parallèle // Supprimer le commentaire des lignes suivantes: DispatchQueue.main.async ( execute: { ... }) // DispatchQueue.main.async
Action 4.2 – Testons l’application.
Vous allez remarquer que la scène principale est affichée immédiatement. Précédemment, la scène était affichée suite au traitement de la requête web qui bloquait l’exécution de l’application.
Par contre, le chargement des pochettes de films est encore bloquant.
Utilisons une technique similaire pour charger les images via le réseau Internet.
Action 4.3 – Dans la méthode ‘cellForItemAtIndexPath‘, remplaçons les lignes de code qui charge l’image via une URL par ceci:
cellule.pochetteImage.image = UIImage(named:Globales.LOADING_IMAGE) ImageViaURL.obtenirImage(urlStr: URLFichierImage, uneimage: cellule.pochetteImage)
Action 4.3.1 – Analysons la méthode : ImageViaURL.obtenirImage()
/// ********************************************** static func obtenirImage(urlStr:String, uneimage: UIImageView){ // Préparer et lancer la requête let request = URLRequest(url: NSURL(string:urlStr ) as! URL) let session = URLSession.shared let task = session.dataTask(with: request, completionHandler: {data, response, error -> Void in if (error == nil) { DispatchQueue.main.async ( execute: { if let _data = data { uneimage.image = UIImage(data: _data) } else { uneimage.image = UIImage(named: Globales.NA_IMAGE) } } ) // DispatchQueue.main.async() } else { // erreur d'URL uneimage.image = UIImage(named: Globales.NA_IMAGE) } }) task.resume() // Reprendre le traitement de la session pour qu'elle puisse se terminer. } // obtenirImage
Action 4.4 – À vous de modifier la classe de la scène ‘détails’ pour que le chargement des images ne soit plus bloquant.
(Après un dur labeur, l’aisance)
Il existe, dans le réseau Internet, des classes Swift ‘open source’ qui facilitent le travail avec une structure JSON.
Une de ces classes est proposée par Pinglin Tang. Elle est disponible ici:
https://github.com/SwiftyJSON/SwiftyJSON
La façon la plus simple de rendre la classe JSON disponible dans un projet Swift est d’ajouter les fichiers ‘SwiftyJSON.swift’ et ‘SwiftyJSON.h’ au projet.
Voici un exemple d’utilisation:
Action: Ajoutons le code suivant au projet:
func testerJSON(){ let strURL = "http://prof-tim.cstj.qc.ca/cours/xcode/sources/timgames/api.timgames.php?apikey=\(Globales.CLE_API)&q=&format=json&quant=50" /// Exécuter le traitement suivant en parallèle if let _données = NSData(contentsOf: URL(string: strURL)!) as? Data { let json = JSON(data: _données) if let titre = json["resultats"][0]["titre"].string { print("Le titre du premier Item est \(titre)") } let imagePochette = json["resultats"][0]["pochettes"]["grande"].string print(imagePochette) for item in json["resultats"].arrayValue { print("-------------------------") print("Titre " + item["titre"].string!) } } }
Action: Testons le code
En utilisant la classe JSON, il faut afficher le titre et le nom des suiveux de tous les items.
Par exemple,
----------------------------------- Titre: the Zzigzbzxqwk suiveux: Jhasvei jp suiveux: Lzkrule ----------------------------------- Titre: the Baoa xhz hx suiveux: Qdhamithnb suiveux: Iysuycm suiveux: Spruqgwq suiveux: Deqdmnv suiveux: Qvwcd suiveux: Plssu ----------------------------------- Titre: the Nzjlqerpmzylbd -----------------------------------
import Alamofire import RxSwift import RxCocoa import SwiftyJSON /// var resultat:JSON? /// let strURL = "http://prof-tim.cstj.qc.ca/cours/xcode/sources/timgames/api.timgames.php?apikey=ceciEstUltraSecret&q=&format=json&quant=5" Alamofire.request(strURL).response { response in // method defaults to `.get` debugPrint(response) self.resultat = JSON(data: response.data!) // print(self.resultat as Any) for item in (self.resultat?["resultats"].arrayValue)! { print("-------------------------") print("Titre " + item["titre"].string!) for suiveux in item["suiveux"].arrayValue { print("\tsuiveux: \(suiveux["nom"].stringValue)") } // for suiveux } // for item } // Alamofire.request(strURL).response
Voici une liste d’APIs disponibles via le web:
http://www.programmableweb.com/apis/directory
http://query.yahooapis.com/v1/public/yql?q=select%20item%20from%20weather.forecast%20where%20location%3D%22CAXX0301%22&format=json
Ce qui donne la météo pour Montréal (CAXX0301)
Voir: http://developer.yahoo.com/yql/
http://wxdata.weather.com/wxdata/search/search?where=montr
http://query.yahooapis.com/v1/public/yql?q=select%20%2a%20from%20yahoo.finance.quotes%20where%20symbol%20in%20%28%22YHOO%22%2C%22AAPL%22%2C%22GOOG%22%2C%22MSFT%22%29%0A%09%09&env=http%3A%2F%2Fdatatables.org%2Falltables.env&format=json
Ce qui donne les cotes de Yahoo, d’Apple et d’Amazon et de microsoft.
http://finance.yahoo.com/webservice/v1/symbols/allcurrencies/quote?format=json
http://api.tou.tv/v1/toutvapiservice.svc/json/GetPageRepertoire
Ce qui donne le répertoire de tou.tv
Voir: https://code.google.com/p/tou-tv-for-boxee/wiki/Api
Document par Alain Boudreault (c) 2016