Développer une application iOS affichant des flux RSS
Nous allons créer une application permettant de consulter à partir de votre iPhone ou votre iPad les news du site de L’Informaticien. La description du projet dans sa globalité nécessitant plusieurs articles, celui-ci constitue la première partie de cet exemple très concret de développement iOS.
Nous allons voir dans cet article comment créer une application capable de lire un flux d’informations RSS provenant du site web de L’Informaticien ( www.linformaticien.com). Nous afficherons les articles dans une vue table et la sélection de l’un d’entre eux l’affichera intégralement. Nous allons ici poser les bases et les grands principes du développement à réaliser, mais ne nous commencerons à coder à proprement parler avec le XCode que dans la deuxième partie, c’est-à-dire dans le prochain article.
Définition du projet
Tout d’abord, comme pour tout projet, il faut définir dans ses grandes lignes l’application que nous voulons obtenir au final. L’application sera identifiée par le logo de L’Informaticien. En cliquant dessus, l’utilisateur (le « client ») verra s’afficher sur son smartphone ou sa tablette la liste des dernières news avec leur titre, une icône associée et un bref descriptif. Le titre général sera le nom du site (L’Informaticien) avec son design habituel. La sélection d’un article l’affichera dans son intégralité, avec reprise de son titre tout en haut de l’écran. L’utilisateur pourra revenir aisément à la liste à l’aide d’un bouton bien en vue. De la vue affichant un article en particulier, l’utilisateur pourra le partager sur les réseaux sociaux auxquels il est éventuellement inscrit (Facebook, Twitter, LinkedIn, Viadeo…). Ajoutons à cela que, pour les besoins de la cause – financière, vous l’aviez deviné –, une ou plusieurs publicités mobiles devront être affichées entre les articles dans l’affichage général de l’application. Les bandeaux publicitaires devront s’afficher à intervalle régulier, encore à définir – tous les 4 intervalles, par exemple. Dans l’affichage d’un article en particulier, un autre bandeau, fixe cette fois, et généralement plus spécifique au contexte de l’article, devra s’afficher en bas de la vue. Ajoutons enfin que le design de l’application devra respecter dans l’ensemble la charte graphique dudit site. Avec ces quelques éléments, nous devrions pouvoir commencer à travailler.
Organisation du projet
Le travail à réaliser peut être divisé en deux grandes parties. Dans la première, nous allons programmer la connexion et la récupération des données depuis un service web et leur utilisation dans la création des objets nécessaire au projet. Dans la seconde, les données web seront affichées à l’aide de la classe UIWebView. Ainsi, nous respectons qui plus est l’architecture ou modèle de conception (design pattern) MVC quasi-incontournable dans tout développement Objective-C/Cocoa et si pratique pour faire évoluer aisément une application sans devoir tout reprogrammer.
Les services web et l’infrastructure HTTP
Un navigateur, ou explorateur, échange des messages avec les serveurs internet en se basant sur le protocole HTTP. Le dialogue le plus élémentaire entre un navigateur client et un serveur consiste à l’envoi, du premier vers le deuxième, d’une requête contenant une adresse URL. Le serveur répond en transmettant les données de la page concernée. Le navigateur met ces données (HTML, images, XML…) en forme avant de les afficher. Une requête légèrement plus complexe comprendra d’autres paramètres tels que des données de formulaire. Le serveur traitera alors ces paramètres afin de renvoyer une page web dynamique. Notre application iOS doit donc exploiter l’infrastructure HTTP et dialoguer avec un serveur web. Le service web constitue le côté serveur de l’application. L’application côté client va échanger des messages – des requêtes dans un sens et des réponses à ces requêtes dans l’autre sens – avec le service web, et tout ceci via le protocole HTTP. Le HTTP n’est pas un protocole « analytique », du coup les données transportées peuvent être complexes sans ralentir pour autant le transfert. Elles sont le plus souvent au format XML ou JSON (JavaScript Object Notation). À vous de choisir le format qui vous convient, du moins si vous avez le contrôle du serveur web. Sinon, vous devrez vous adapter.
Les classes NSURL, NSURLRequest et NSURLConnection
Le projet va utiliser principalement les classes NSURL, NSURLRequest et NSURLConnection afin d’extraire les données du serveur. Chacune de ces trois classes assure un rôle vital dans la communication entre clients et serveurs web. L’emplacement au format URL d’une application web est stocké dans une instance de la classe NSURL. Cette adresse est généralement la fusion de l’adresse de base, du nom de l’application web concernée et des éventuels arguments à lui transmettre. Les données nécessaires à la communication avec le serveur sont mémorisées dans une instance de la classe NSURLRequest. Ces données comportent un objet de type NSURL, des règles de mise en cache, le délai limite pour le temps de réponse du serveur ainsi que des données complémentaires transmises via le protocole HTTP. L’établissement de la connexion à proprement parler avec le serveur web est pris en charge par une instance de la classe NSURLConnection. Les informations enregistrées dans l’instance NSURLRequest sont envoyées par l’instance de NSURLConnection qui récupère ensuite la réponse émise par le serveur. Le format des requêtes de service web varie en fonction des choix des concepteurs du dit service. Pas de véritable règle générale : il faut systématiquement récupérer la documentation du service web concerné afin de savoir comment formuler vos requêtes. Le dialogue ne peut s’établir que si l’application cliente envoie les requêtes dans le format adéquat. Nous décrirons précisément celui du site mobile de L’Informaticien dans la deuxième partie, c’est-à-dire dans le prochain article.
Formatage des chaînes de caractères
Pour que vos chaînes de caractères soient compatibles avec le format d’une URL, elles ne doivent inclure aucun espace ni guillemet. Ceux-ci doivent donc être impérativement remplacés par des séquences d’échappement. En voici un exemple : NSString *search = @"Have some \"Fun\""; NSString *escaped = [search stringByAddingPercentEsca pesUsingEncoding:NSUTF8StringEncoding]; Ce qui va donner après traitement : "Have%20some%20 %22Fun%22" Le processus est le suivant : une requête est envoyée au serveur de L’informaticien qui la traite et renvoie des données au format XML. Celles-ci contiendront un nombre déterminé d’articles. L’auteur de la requête (représenté dans le code par ListViewController) pourra ainsi « peupler » sa vue table (TableView) avec les titres et un filet de présentation pour chaque article. Vous allez devoir ajouter une variable d’instance pour gérer la connexion et une autre pour stocker les données à récupérer dans
ListViewController.h. Une nouvelle méthode, recupEntrees, doit également être déclarée : @interface ListViewController : UITableViewController { NSURLConnection *connection ; NSMutableData *xmlData ; } - (void) recupEntrees ; @end
Fonctionnement de NSURLConnection
NSURLConnection est capable de dialoguer de manière synchrone ou asynchrone avec un serveur web. La problématique qui se pose avec les connexions synchrones tient dans la variabilité importante de la durée des échanges de données entre un client et le serveur distant. Une connexion synchrone bloquera votre connexion client tant qu’elle n’a pas reçu la réponse du serveur. Sauf contrainte particulière motivée par les besoins spécifiques de l’application, il est donc préférable de mettre en place un dialogue asynchrone. Nous allons voir comment faire avec la classe NSURLConnection. Lorsque l’on crée une instance de NSURLConnection, il faut spécifier l’emplacement de l’application web avec laquelle nous voulons établir un dialogue et fournir les données à transmettre au serveur. Il faut, de plus, créer un délégué. Lors de l’établissement d’une connexion avec le serveur, NSURLConnection va initialiser une connexion à l’emplacement spécifié et commencer à transmettre les données et à recevoir celles en retour. L’instance met alors à jour son délégué en lui transmettant les informations nécessaires à chaque étape intermédiaire. Nous avons déclaré la méthode recupEntrees, il faut maintenant l’implémenter. Nous allons pour cela ouvrir ListViewController.m et créer une requête NSURLRequest qui va se connecter à l’adresse que nous allons lui spécifier. Nous allons ensuite lui demander de renvoyer les derniers articles au format RSS 2.0. Il ne restera (presque) plus qu’à créer un objet NSURLConnection pour qu’il transfère la requête au serveur. Cela devrait donner quelque chose de ce genre : - (void) recupEntrees {
// Crée un conteneur pour les données renvoyées par le service
xmlData = [[NSMutableData alloc] init];
// Crée une URL qui va formuler une demande au service
// Notez au passage que nous pouvons concaténer des chaînes de caractères littérales
// ensemble sur plusieurs lignes afin d’obtenir une instance NSString unique
NSURL *url = [NSURL URLWithString:
@" http://www.linformaticien.com/ actualites...<adresse du service web>"
@"param1=valeurParam1& param2=valeurParam2& param3=valeurParam3&...."];
// les param< X> correspondent aux arguments du service web
// Il faut ensuite employer cette URL dans une instance de la classe NSURLRequest...
NSURLRequest *req = [NSURLRequest requestWithURL:url];
// ... puis établir une connexion qui va échanger cette requête contre des données en // provenance de l’URL connection = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:YES]; } @end Un échange – ou requête/données entre le client et le serveur – est effectué à chaque création de ListViewController. Nous allons maintenant redéfinir la méthode initWithStyle: dans ListViewController.m : - (id)initWithStyle:(UITableViewStyle)style { self = [super initWithStyle:style]; if (self) {
[self recupEntrees]; } return self;
Traitement et affichage des données XML
Pour l’instant, notre programme tel qu’il est défini dans ses grandes lignes devrait réussir à établir une connexion avec le service web visé et récupérer les derniers articles. Bien, mais… cela ne va pas les afficher pour autant ! Il faut ensuite créer les méthodes déléguées pour l’instance de NSURLConnection qui vont récupérer les données XML rapatriées par la requête. Le délégué de NSURLConnection a pour tâches la surveillance de la connexion et la récupération des données renvoyées à la fin du traitement des requêtes – au format JSON ou XML. Les données en question sont transmises par blocs et c’est au délégué de « recoller les morceaux ».
Mise en forme des données par connection:didReceiveData:
Nous devons donc rassembler ces données et les mettre en ordre dans une variable d’instance (xmlData). Ouvrez encore ListViewController.m pour implémenter cette fois la méthode connection:didReceiveData: - (void)connection:(NSURLConnection *)conn didReceiveData:(NSData *)data { // Ajoute le bloc de données entrant au conteneur // Les données sont rangées dans le bon ordre [xmlData appendData:data];
Définition des instructions de connectionDidFinishLoading:
Une fois que toutes les données d’une requête ont été récupérées auprès du service web, un message connectionDidFinishLoading: est envoyé au délégué. L’exécution de cette méthode garantit la récupération de la globalité des informations transmises par le service web. Nous allons spécifier
à présent les instructions de connectionDidFinishLoading:, toujours dans ListViewController.m, en vue d’afficher une représentation des informations récupérées sur la console et de s’assurer de la bonne réception de la réponse : - (void)connectionDidFinishLoading:(NSURLConnection *) conn { // Test de récupération des données NSString *xmlCheck = [[NSString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding]; NSLog(@"xmlCheck = %@", xmlCheck);
Gestion des échecs de connexion avec connection:didFailWithError:
Une connexion peut échouer, ne l’oublions pas. Si une instance de NSURLConnection ne réussit pas à se connecter à un service web, elle va envoyer le message connection:didFailWithError: à son délégué. Ce message est émis systématiquement en cas d’échec d’une connexion si le serveur est introuvable ou qu’il n’y a pas d’accès internet. Pour les autres types d’erreurs, telles que des erreurs de formats de données, les informations de description de l’erreur rencontrée sont renvoyées dans connection:didReceiveData:. Nous allons implémenter la méthode connection:didFailWithError: dans ListViewController.m pour être averti dans l’application lorsqu’un échec de connexion se produit : - (void)connection:(NSURLConnection *)conn
didFailWithError:(NSError *)error
{ // Libère l’objet connexion désormais inutile connection = nil; // Libère l’objet xmlData désormais inutile xmlData = nil; // Récupère la description de l’objet error reçu NSString *errorString = [NSString stringWithFormat:@"Echec lors de l’extraction: %@",
[error localizedDescription]];
// Créer et afficher un message d’avertissement spécifiant l’erreur
UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"Erreur" message:errorString delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
Analyse des données XML à l’aide de la classe NSXMLParser
L’analyse des données XML se fait à l’aide de la classe NSXMLParser. Celle- ci attend en entrée un bloc de données au format XML qu’elle traite ligne par ligne. Lorsqu’elle détecte une information « intéressante », elle en informe son délégué qui va tenter de l’interpréter en fonction des règles de l’application.
Lancement de l’analyse ave la méthode connectionDidFinishLoading:
Nous devons modifier le code de connectionDidFinishLoading: pour lancer l’analyse des données XML au lieu de les afficher et faire pointer le délégué de l’analyseur sur l’instance de ListViewController : - (void)connectionDidFinishLoading:(NSURLConnection *) conn {
// Crée l’objet analyseur avec les données envoyées par le service web
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData]; // Attribue un délégué au parser [parser setDelegate:self];
// Lance l’analyse. Le document va être analysé et le délégué de NSXMLParser
// va recevoir tous les messages de type delegate qui lui ont été envoyés
// avant l’exécution de cette ligne d’instruction (qui est bloquante)
[parser parse]; // Supprime les données XML désormais inutiles xmlData = nil; // Supprime la connexion désormais inutile connection = nil; // Recharge la table, vide pour l’instant [[self tableView] reloadData]; Le délégué du parser ( analyseur), le contrôleur ListViewController, recevra un message à chaque fois que l’analyseur détecte un nouvel élément ou une chaîne dans un élément ou encore la fin d’un élément. Si, par exemple, le parser lit ce code XML :
<title>L’Informaticien/title> il va envoyer à son délégué trois messages, du style "Nouvel élément : ’title", suivi de "Chaîne : ’L’informaticien’ et "Fin de l’élément ’title’ "
Le protocole NSXMLParserDelegate
Ces messages sont définis dans le protocole NSXMLParserDelegate : // Nouvel élément - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict; // Chaîne - (void)parser:(NSXMLParser *)parser
foundCharacters:(NSString *)string; // Fin de l’élément - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict
Construction d’une arborescence d’objets
Le contrôleur ListViewController construit une arborescence d’objets correspondant à la structure des données XML de la source. L’analyse de ces données doit conduire à la génération d’une instance de la classe RSSChannel contenant des instances de RSSItem. Les étapes de construction de cet arbre sont les suivantes : élément channel, une instance de la classe RSSChannel doit être créée ; description se trouvant à l’intérieur d’un élément channel, nous devons renseigner la propriété adéquate de l’instance RSSChannel ; il faut créer une instance de la classe RSSItem et l’ajouter au tableau items de l’objet de type RSSChannel ; situé dans un élément item, la propriété correspondante de l’instance RSSItem doit être renseignée.
La classe UIWebView
Tout objet RSSItem mémorise un titre ainsi qu’un lien vers la page web référencée par le corps du message. Pour que le programme puisse ouvrir une instance de navigateur et afficher cette page sans même devoir démarrer Safari, il faut utiliser la classe UIWebView. Cette classe permet d’afficher du contenu web. Au passage, le navigateur Safari s’appuie lui-même sur elle. Pour arriver au résultat escompté, il faut créer un contrôleur de vue qui gèrera une instance d’UIWebView. La sélection dans la vue table d’un élément RSSItem devra déclencher l’empilement du contrôleur de vue correspondant sur la pile de navigation et le chargement du contenu du lien stocké dans l’objet RSSItem.
Création d’une classe WebViewController
Pour ce faire, nous devons créer une nouvelle classe que nous appellerons WebViewController. Après l’avoir créée via le menu de XCode, ouvrez WebViewController.h. Nous allons y déclarer une propriété à l’aide du mot-clef property, pas une simple variable d’instance, et faire hériter WebViewController de la classe UIViewController : @interface WebViewController : UIViewController @property (nonatomic, readonly) UIWebView *webView; @end
Redéfinition de la méthode loadView de la classe WebViewController
Nous allons ensuite ouvrir WebViewController.m et redéfinir loadView en vue de créer une instance de UIWebView qui va devenir la vue de contrôleur, ce qui devrait donner quelque chose de ce genre : #import "WebViewController.h" @implementation WebViewController - (void)loadView {
// Crée une instance de UIWebView adaptée à la taille de l’écran
CGRect screenFrame = [[UIScreen mainScreen] applicationFrame];
UIWebView *wv = [[UIWebView alloc] initWithFrame:screenFrame];
// Retaille le contenu web afin de le faire correspondre aux limites de la vue web [wv setScalesPageToFit:YES]; [self setView:wv]; } - (UIWebView *)webView {
return (UIWebView *)[self view]; } @end
Déclaration d’une nouvelle propriété dans ListViewController
Il faut ensuite ouvrir ListViewController.h afin de déclarer une nouvelle propriété pour la classe ListViewController : @class WebViewController; @interface ListViewController : UITableViewController <NSXMLParserDelegate> { NSURLConnection *connection; NSMutableData *xmlData; RSSChannel *channel; } @property (nonatomic, strong) WebViewController *webViewController; - (void)recupEntrees; @end
Modification de ListViewController.m
Ouvrez ListViewController.m afin de spécifier l’importation du fichier header (.h) à l’aide d’une instruction #import et la création automatiques des accesseurs de la propriété webViewController grâce à une instruction @synthesize : #import "WebViewController.h" @implementation ListViewController @synthesize webViewController; La place nous manquant pour continuer ici, nous faisons une pause pour le moment. Et reprendrons le descriptif du développement de notre application comme annoncé dans le prochain numéro de L’Informaticien.