Lorsque vous utiliserez des webservices, vous aurez très certainement besoin de parser des fichiers, qu’ils soient XML ou JSON. Ce tutoriel vous apprend à le faire
Utiliser XML
Parsons un fichier XML contenant des pays. Créez un nouveau projet Window-based Application que vous nommerez XML. Écrivez ces quelques lignes dans un fichier .xml (capitales.xml) et ajoutez-le au projet (rappel : glisser-déposer le fichier directement dans Xcode).
<?xml version=1.0 encoding=UTF-8?>
<Monde>
<pays>
<nom>France</nom>
<capitale>Paris</capitale>
<habitants>60M</habitants>
</pays>
<pays>
<nom>Suisse</nom>
<capitale>Berne</capitale>
<habitants>7,5M</habitants>
</pays>
<pays>
<nom>Allemagne</nom>
<capitale>Berlin</capitale>
<habitants>82M</habitants>
</pays>
</Monde>
Il existe un parser XML intégré directement dans le framework Foundation : NSXMLParser. Nous allons donc nous en servir. Pour cela, créez une nouvelle classe, subclass of NSObject que vous nommerez XMLParser.
Implémentons cette classe en commençant par la méthode qui lancera le parsage :
- (
void
)parseXMLFileAtPath:(
NSString
*)path {
stories = [[
NSMutableArray
alloc] init];
// 1
NSURL
*xmlURL = [
NSURL
fileURLWithPath:path];
textParser = [[
NSXMLParser
alloc] initWithContentsOfURL:xmlURL];
[textParser setDelegate:
self
];
// 2
[textParser parse];
// 3
}
Chaque pays présent dans le fichier XML va être ajouté dans un tableau : countries, que l’on initialise ligne 1.
Ligne 2, la classe NSXMLParser propose un protocole delegate, et on choisit de l’implémenter. C’est obligatoire pour que le parser soit opérationnel. Enfin, on lance le parsage ligne 3.
Regardons maintenant le .h où l’on déclare tous les objets nécessaires pour construire le tableau des pays.
#import <Foundation/Foundation.h>
#define kPays @"pays"
#define kCapitale @"capitale"
#define kNom @"nom"
#define kPopulation @"habitants"
@protocol
XMLParserDelegate;
@interface
XMLParser :
NSObject
<
NSXMLParserDelegate
> {
NSXMLParser
*textParser;
NSMutableArray
*stories;
// a temporary item; added to the "stories" array one at a time, and cleared for the next one
// un objet temporaire (ici un pays).
// il est ajouté au tableau "stories" puis effacé pour le nouveau pays
NSMutableDictionary
*item;
// On va parser le document de haut en bas.
// On va donc collecter chaque sous-élements, les sauver dans item, puis ajouter dans le tableau
NSString
*currentElement;
NSMutableString
*currentCountry;
NSMutableString
*currentPopulation;
NSMutableString
*currentCapital;
id
<XMLParserDelegate> delegate;
}
- (
void
)parseXMLFileAtPath:(
NSString
*)path;
@property
(
nonatomic
, retain)
NSMutableArray
*stories;
@property
(
nonatomic
, assign)
id
<XMLParserDelegate> delegate;
@end
@protocol
XMLParserDelegate <
NSObject
>
- (
void
) xmlParserdidFinishParsing;
@end
N’oubliez pas ces lignes dans le .m :
#import "XMLParser.h"
@implementation
XMLParser
@synthesize
stories;
@synthesize
delegate;
Ensuite, implémentons les méthodes du delegate. Commençons par celle appelée lorsque le parser rencontre un nouvel élément :
- (
void
)parser:(
NSXMLParser
*)parser didStartElement:(
NSString
*)elementName namespaceURI:(
NSString
*)namespaceURI qualifiedName:(
NSString
*)qName attributes:(
NSDictionary
*)attributeDict{
if
(currentElement)
{
[currentElement release];
currentElement =
nil
;
}
currentElement = [elementName
copy
];
// 1
if
([elementName isEqualToString:kPays]) {
// 2
//On efface notre cache qui est item
if
(item)
{
;
item =
nil
;
}
if
(currentCountry)
{
[currentCountry release];
currentCountry =
nil
;
}
if
(currentCapital)
{
[currentCapital release];
currentCapital =
nil
;
}
if
(currentPopulation)
{
[currentPopulation release];
currentPopulation =
nil
;
}
item = [[
NSMutableDictionary
alloc] init];
currentPopulation = [[
NSMutableString
alloc] init];
currentCapital = [[
NSMutableString
alloc] init];
currentCountry = [[
NSMutableString
alloc] init];
}
}
Dans cette méthode, on met à jour l’élément courant rencontré ligne 1. Ensuite, si cet élément (la balise rencontrée) est égale à Pays ligne 2, on sait que l’on rencontre un nouveau pays. On remet donc à zéro tous les objets qui servent à construire item (un dictionnaire contenant les données du pays).
Après avoir détecté la balise Pays, il faut mettre en cache les valeurs des balises Nom, Capitale et Population. Pour cela, implémentez la méthode suivante :
- (
void
)parser:(
NSXMLParser
*)parser foundCharacters:(
NSString
*)string{
// on sauve les éléments du pays pour l'item en cours
if
([currentElement isEqualToString:kCapitale])
[currentCapital appendString:string];
else
if
([currentElement isEqualToString:kNom])
[currentCountry appendString:string];
else
if
([currentElement isEqualToString:kPopulation])
[currentPopulation appendString:string];
}
Ensuite, définissons la méthode appelée lorsque l’on rencontre une balise de fermeture (exemple : </ balise>). Dans cette méthode, lorsque l’on ferme pays, on sauve les valeurs des balises nom, capitale et population pour le pays en question, puis on ajoute ce pays dans le tableau des pays.
- (
void
)parser:(
NSXMLParser
*)parser didEndElement:(
NSString
*)elementName namespaceURI:(
NSString
*)namespaceURI qualifiedName:(
NSString
*)qName{
if
([elementName isEqualToString:kPays]) {
// on sauve les valeurs dans item, qui est ensuite ajouté dans le tableau stories
;
;
;
[stories addObject:item];
}
}
Enfin, lorsque le parser a fini de parcourir le document, on choisit d’avertir le delegate. En effet, nous allons présenter les données parsées grâce à une table view. Cette méthode est appelée sur le thread principal, car nous allons par la suite parser dans un thread détaché pour l’occasion.
- (
void
)parserDidEndDocument:(
NSXMLParser
*)parser {
NSLog
(@
"C'est fini !"
);
NSLog
(@
"stories a %d pays"
, [stories count]);
[
self
performSelectorOnMainThread:
@selector
(tellTheDelegateItIsFinished) withObject:
nil
waitUntilDone:
NO
];
}
- (
void
) tellTheDelegateItIsFinished {
[delegate xmlParserdidFinishParsing];
}
Et le dealloc alors ?
- (
void
) dealloc {
[stories release];
[textParser release];
;
[currentCapital release];
[currentCountry release];
[currentPopulation release];
[
super
dealloc];
}
Créons maintenant le table view controller qui gérera l’affichage des données du fichier XML. Pour cela, ajoutez un nouveau fichier UIViewController, de type UITableViewController, que vous nommerez XMLTableViewController. Dans l’application delegate, ajoutez ces lignes :
Dans le .h
#import <UIKit/UIKit.h>
#import "XMLTableViewController.h"
@interface
XMLAppDelegate :
NSObject
<UIApplicationDelegate> {
XMLTableViewController *tableViewController;
}
@property
(
nonatomic
, retain)
IBOutlet
UIWindow *window;
@end
Dans le .m
- (
BOOL
)application:(UIApplication *)application didFinishLaunchingWithOptions:(
NSDictionary
*)launchOptions
{
// Override point for customization after application launch.
tableViewController = [[XMLTableViewController alloc] initWithStyle:UITableViewStylePlain];
[
self
.window addSubview:tableViewController.view];
[
self
.window makeKeyAndVisible];
return
YES
;
}
- (
void
)dealloc
{
[tableViewController release];
[_window release];
[
super
dealloc];
}
Ensuite, modifiez ces méthodes dans le table view controller fraîchement créé :
- (
void
)viewDidLoad
{
[
super
viewDidLoad];
self
.clearsSelectionOnViewWillAppear =
NO
;
NSString
*xmlFilePath =[[
NSBundle
mainBundle] pathForResource:@
"capitales"
ofType:@
"xml"
];
// 1
[
self
performSelectorInBackground:
@selector
(parseXMLFile:) withObject:xmlFilePath];
}
- (
void
)parseXMLFile:(
NSString
*)thePath {
NSAutoreleasePool
*pool = [[
NSAutoreleasePool
alloc] init];
// 2
parser = [[XMLParser alloc] init];
parser.delegate =
self
;
// 3
if
([parser.stories count] == 0)
{
[parser parseXMLFileAtPath:thePath];
}
[pool release];
}
Ligne 1, on choisit donc de parser le document dans un thread séparé.Ainsi, le thread principal n’est pas dédié au parsage, et reste libre pour intercepter et réagir aux événements utilisateurs, pour afficher des éléments à l’écran.
En détachant un thread, on doit créer un bassin d’autorelease, ce que l’on fait ligne 2. En effet, le bassin d’autorelease créé dans le fichier main.m n’est disponible que pour le thread principal. Pour permettre au parser d’appeler le rafraîchissement de l’affichage des données, on set la propriété delegate ligne 3.
Enfin, pour afficher les données :
#pragma mark - XMLParser delegate
- (
void
) xmlParserdidFinishParsing {
[
self
.tableView reloadData];
}
#pragma mark - Table view data source
- (
NSInteger
)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return
1;
}
- (
NSInteger
)tableView:(UITableView *)tableView numberOfRowsInSection:(
NSInteger
)section
{
// Return the number of rows in the section.
return
[parser.stories count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(
NSIndexPath
*)indexPath
{
static
NSString
*CellIdentifier = @
"Cell"
;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if
(cell ==
nil
) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
NSDictionary
*pays = [parser.stories objectAtIndex:[indexPath row]];
cell.textLabel.text = [
NSString
stringWithFormat:@
"%@ %@ %@"
, [pays objectForKey:kNom], [pays objectForKey:kCapitale], [pays objectForKey:kPopulation]];
return
cell;
}
N’oubliez pas le dealloc !
- (
void
)dealloc
{
[
super
dealloc];
[parser release];
}
Pour information, voici le .h :
#import <UIKit/UIKit.h>
#import "XMLParser.h"
@interface
XMLTableViewController : UITableViewController <XMLParserDelegate> {
XMLParser *parser;
}
@end
Compilez et lancez votre application, vous devriez obtenir une liste comme ceci :
Pour parser un fichier XML directement depuis une source Internet, il suffit de créer un objet NSURL du fichier XML et le passer directement en paramètre de la méthode :
textParser = [[
NSXMLParser
alloc] initWithContentsOfURL:xmlURL];
Utiliser le JSON
L’iPhone et l’iPad sont des appareils mobiles et connectés qui permettent un accès facile à Internet. Bien que l’on puisse utiliser le navigateur intégré, grâce à une UIWebView, il est bien souvent plus confortable pour l’utilisateur de lancer une application spécifique à l’iPhone, dite optimisée, pour accéder aux informations qui l’intéressent, comme sa page Facebook ou son fil Twitter.
Cet accès est possible grâce aux webservices qui sont en quelque sorte des sites web spécifiques, sans mise en page, qui nous permettent d’obtenir les informations nécessaires. Beaucoup de sites proposent des API qui utilisent le JSON comme système de fichier en plus du traditionnel XML.
Voici quelques exemples :
Twitter.http://apiwiki.twitter.com/Twitter-Search-API-Method%3A-trends-current
Facebook. http://wiki.developers.facebook.com/ind … mments.get
Météo. http://www.geonames.org/export/JSON-webservices.html
Pourquoi le JSON ?
La question n’est pas anodine. Le JSON a séduit les développeurs par sa simplicité et sa rapidité. Même si ce n’est pas une technologie mise en avant par Apple, qui utilise du XML pour les plist par exemple, le JSON a l’avantage d’être un format plus compact et donc plus rapide lors de son téléchargement sur un webservice.
Comparaison sur un tableau de score par exemple :
XML :
<?xml version=
"1.0"
encoding=
"UTF-8"
?>
<Resultats>
<Joueur>
<Nom>Jean Martin</Nom>
<Score>10000</Score>
</Joueur>
<Joueur>
<Nom>Pierre Dupond</Nom>
<Score>9000</Score>
</Joueur>
<Joueur>
<Nom>Alice Bateau</Nom>
<Score>8500</Score>
</Joueur>
</Resultats>
Longueur : 271 caractères.
JSON :
{
"resultats"
:{
"joueurs"
:[
{
"nom"
:
"Jean Martin"
,
"score"
:10000
},
{
"nom"
:
"Pierre Dupond"
,
"score"
:
"9000"
},
{
"nom"
:
"Alice Bateau"
,
"score"
:
"8500"
}
]
}
}
Longueur : 194 caractères
Que peut-on conclure de cette comparaison ? Le XML, de par sa taille, va être environ 50 % plus lent au téléchargement pour la même information, ce qui est loin d’être négligeable sur un objet mobile.
Utilisation sur iPhone
La méthode présentée n’est pas LA méthode mais elle a le mérite de fonctionner parfaitement. Pour corser le tout, nous allons faire une application compatible iPhone et iPad (Universelle).
Lancer Xcode et faire : File > New > New Project > Window-based Application et choisir Universal dans Device Family. Appelons ce projet JSON_Tuto.
Rappelez-vous, je vous ai dit qu’Apple fonctionne avec XML pour le moment !
Il existe plusieurs frameworks pour traiter le JSON sur iPhone, notre préférence va à celui de dispo- nible à cette adresse : http://stig.github.com/json-framework/.
Il a l’avantage d’avoir fait ses preuves dans beaucoup d’applications.
Téléchargez la dernière version, vous devriez arriver sur la fenêtre représentée à la figure suivante.
Pour vous en servir, le plus simple est de glisser directement le contenu du dossier Classes dans votre projet Xcode (et le copier).
Ca devrait ressembler à ça :
Nous allons maintenant intégrer un fichier en JSON dans notre projet afin de pouvoir l’exploiter.
1. Sur JSON_Tuto faire ctrl+Clic > New File, pour ajouter un nouveau fichier où nous écrirons le JSON.
2. Choisir ensuite other dans la colonne de gauche puis Empty File.
3. Vous pouvez l’appeler JSON.txt par exemple et ensuite y intégrer les informations que nous avons utilisées dans notre comparaison entre le JSON et le XML.
Vous devriez obtenir quelque chose de comparable à ceci :
Nous allons maintenant afficher ces données dans une UITableView. Pour ce faire : ctrl+Clic > > New File. Choisir ensuite Cocoa Touch Class > UIViewController et choisir UITableViewController subclass.
Nommez ce fichier TableViewController.
Il reste à ajouter cette vue aux deux Appdelegate (un pour l’iPad et l’autre pour l’iPhone). Mais vous remarquerez que chaque app delegate iPhone et iPad hérite du même. Modifions donc JSON_tutoAppDelegate.h
#import <UIKit/UIKit.h>
@class
TableViewController;
@interface
JSON_TutoAppDelegate :
NSObject
<UIApplicationDelegate> {
TableViewController *tableViewController;
}
@property
(
nonatomic
, retain)
IBOutlet
UIWindow *window;
@property
(
nonatomic
, retain) TableViewController *tableViewController;
@end
#import "JSON_TutoAppDelegate.h"
#import "TableViewController.h"
@implementation
JSON_TutoAppDelegate
@synthesize
window=_window;
@synthesize
tableViewController;
- (
BOOL
)application:(UIApplication *)application didFinishLaunchingWithOptions:(
NSDictionary
*)launchOptions
{
// Override point for customization after application launch.
self
.tableViewController = [[[TableViewController alloc] initWithStyle:UITableViewStylePlain] autorelease];
[
self
.window addSubview:tableViewController.view];
[
self
.window makeKeyAndVisible];
return
YES
;
}
- (
void
)dealloc
{
[tableViewController release];
[_window release];
[
super
dealloc];
}
@end
Avant d’aller plus loin, vous allez compiler votre projet pour le tester.
C’est bon ?
Ok, changez ça dans TableViewController.m :
- (
BOOL
)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return
YES
;
}
Cette dernière méthode permet à votre application de fonctionner dans toutes les orientations. Il est vivement recommandé de l’implémenter dans les applications iPad, d’après le guide d’Apple concernant l’Interface Homme Machine.
Vous devriez maintenant obtenir une liste de données vide et qui fonctionne dans toutes les orientations.
Remplir la liste depuis le fichier JSON
Afin de faciliter le travail et de bénéficier de toute la puissance d’un langage objet, nous allons créer un objet Joueur. Pour cela, faites ctrl+Clic > New File. Créez un fichier Objective-C class héritant de NSObject.
Appelez cette classe Joueur tout simplement !
Passons maintenant à l’implémentation. Notre classe Joueur va respecter la structure du JSON et va ainsi posséder un nom et un score :
Dans Joueur.h
#import <Foundation/Foundation.h>
@interface
Joueur :
NSObject
{
NSString
*nom;
NSInteger
score;
}
@property
(
nonatomic
, retain)
NSString
*nom;
@property
NSInteger
score;
@end
Joueur.m
#import "Joueur.h"
@implementation
Joueur
@synthesize
nom;
@synthesize
score;
-(
void
)dealloc {
[nom release];
[
super
dealloc];
}
@end
Le traitement du JSON
C’est la section la plus intéressante. Un UITableViewController nécessite un tableau contenant les données à afficher.
Dans notre cas, nous travaillerons avec une liste d’objets Joueur.
Dans TableViewController.h
#import <UIKit/UIKit.h>
@interface
TableViewController : UITableViewController {
NSMutableArray
*dataToDisplay;
}
@end
Dans le format JSON, la chose importante est de distinguer les éléments des tableaux. Voici deux règles qui permettent de s’en sortir facilement : Règle 1 : le format normal est :
{
"clé1"
:
"élément1"
,
"clé2"
:
"élément2"
}
Règle 2 : un élément peut être un tableau d’éléments et un tableau s’écrit :
[{
"sous_clé1"
:
"sous_élément1"
} , {
"sous_clé2"
:
"sous_élément2"
}]
Le réflexe est donc [ ] donne un tableau.
Dans notre cas, dans la clé resultat, on a un élément avec la clé Joueur qui contient un tableau de joueurs !
Passons au code :
Dans le .m, ajouter
#import "JSON.h"
#import "Joueur.h"
Ensuite,
- (
void
)viewDidLoad
{
[
super
viewDidLoad];
dataToDisplay = [[
NSMutableArray
alloc] init];
//récupération du chemin vers le fichier contenant le JSON
NSString
*filePath = [[
NSBundle
mainBundle] pathForResource:@
"JSON"
ofType:@
"txt"
];
//création d'un string avec le contenu du JSON
NSString
*myJSON = [[
NSString
alloc] initWithContentsOfFile:filePath encoding:
NSUTF8StringEncoding
error:
NULL
];
//Parsage du JSON à l'aide du framework importé
NSDictionary
*json = [myJSON JSONValue];
//récupération des résultats
NSDictionary
*resultats = [json objectForKey:@
"resultats"
];
//récupération du tableau de Jouers
NSArray
*listeJoueur = [resultats objectForKey:@
"joueurs"
];
//On parcourt la liste de joueurs
for
(
NSDictionary
*dic in listeJoueur) {
//création d'un objet Joueur
Joueur *joueur = [[Joueur alloc] init];
//renseignement du nom
joueur.nom = [dic objectForKey:@
"nom"
];
//renseingement du score
joueur.score = [[dic objectForKey:@
"score"
] intValue];
//ajout à la liste
[dataToDisplay addObject:joueur];
//libération de la mémoire
[joueur release];
}
//à ne pas oublier après l'allocation effectuée au début
[myJSON release];
}
Puis
- (
NSInteger
)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return
1;
}
- (
NSInteger
)tableView:(UITableView *)tableView numberOfRowsInSection:(
NSInteger
)section
{
// Return the number of rows in the section.
return
[dataToDisplay count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(
NSIndexPath
*)indexPath
{
static
NSString
*CellIdentifier = @
"Cell"
;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if
(cell ==
nil
) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
Joueur *joueur = [dataToDisplay objectAtIndex:indexPath.row];
//on affiche le nom et le score
cell.textLabel.text = [
NSString
stringWithFormat:@
"%@ - %d"
,joueur.nom, joueur.score];
return
cell;
}
Et ne pas oublier le dealloc !!
- (
void
)dealloc
{
[
super
dealloc];
[dataToDisplay release];
}
Voilà, vous savez maintenant utiliser le JSON et le XML !