Dans la plupart des applications, nous avons besoin d’accéder à des données de natures diverses : images, textes, sons… Il existe plusieurs méthodes pour réaliser cela. La plus simple, mais surtout la plus sale, est de coder en dur le lien vers la ressource. Nous vous la déconseillons si vous envisager d’écrire une application avec beaucoup de ressources. Coder en dur devient alors vite fastidieux et une source potentielle d’erreurs. La bonne pratique pour manipuler des ressources est souvent de mettre en place une base de données, la plus utilisée étant SQLite.

Chargement des questions

Les premières étapes de l’application iQCM sont décrites dans le livre
Objective-C Créez vos applications iPhone et iPad (Mac 0S, Linux, Windows) – Editions ENI.
La première version de ce QCM contient des questions codées en dur. Il est temps de choisir une solution plus souple.

Code du livre

Pour mémoire :

-(void) chargeQuestions {
 
   Question *question = [[Question alloc]
                          initEnonce: @"MVC signifie..."
                        BonneReponse: @"Modèle Vue Contrôleur"
                           mauvaise1: @"Modèle Varié Contrôlé"
                           mauvaise2: @"Maison Vers Castres"];
   [tableauDeQuestions addObject: question];
   [question release];
 
   question = [[Question alloc]
                initEnonce: @"Le créateur de l'Objective-C\nest..."
              BonneReponse: @"Brad Cox"
                 mauvaise1: @"Steve Jobs"
                 mauvaise2: @"Bill Gates"];
   [tableauDeQuestions addObject: question];
   [question release];
 
   question = [[Question alloc]
                initEnonce: @"L'ancêtre de Cocoa est..."
              BonneReponse: @"NeXtStep"
                 mauvaise1: @"Java"
                 mauvaise2: @"Fortran"];
   [tableauDeQuestions addObject: question];
   [question release];
 
   int i;
   for(i = 0 ; i < [tableauDeQuestions count] ; i++)
      NSLog(@"%@\n", [tableauDeQuestions objectAtIndex:i]);
}

Cette solution n’est évidemment pas satisfaisante si nous souhaitons avoir plusieurs QCM de tailles respectables. Nous avons envisagé deux possibilités de stockage : les fichiers XML et les bases de données SQLite. Notre choix s’est finalement porté sur SQLite qui pourra plus facilement supporter des QCM avec des milliers de questions. Il faut savoir être ambitieux :-D

Création de la base

Nous avons écrit l’ensemble des questions dans le fichier dbSQLite.dump.

La base de données est ensuite obtenue à l’aide de commandes SQL classiques. Le script SQLite commence par BEGIN TRANSACTION afin de réaliser nos actions dans une seule transaction, le fichier se termine par COMMIT pour indiquer la fin de la transaction. La structure de la table de questions Question est décrite après CREATE TABLE. Une question possède un identifiant unique (clé primaire) et 4 attributs au format texte : l’énoncé, la bonne réponse et les 2 mauvaises. Il ne reste plus qu’à insérer les questions une à une dans la base avec la commante INSERT.

Il est souvent recommandé de passer par un éditeur SQLite pour faciliter cette étape, tout spécialement si vous avez une base avec plusieurs tables. J’en profite pour vous dire que Firefox propose le module SQLite Manager. Ce module permet d’avoir un éditeur SQLite multiplateforme bien pratique.

BEGIN TRANSACTION;
CREATE TABLE Question(NumQuestion INTEGER PRIMARY KEY,
				Enonce VARCHAR(200),
				BonneReponse VARCHAR(100),
				mauvaise1 VARCHAR(100),
				mauvaise2 VARCHAR(100));
INSERT INTO "Question" VALUES(	1,
				'MVC signifie...',
				'Modèle Vue Contrôleur',
				'Modèle Varié Contrôlé',
				'Maison Vers Castres');
INSERT INTO "Question" VALUES(	2,
				"Le créateur de l'Objective-C est …",
				'Brad Cox',
				'Steve Jobs',
				'Bill Gates');
INSERT INTO "Question" VALUES(	3,
				"L'ancêtre de Cocoa est…",
				'NeXtStep',
				'Java',
				'Fortran');
...
COMMIT;

Ce fichier permet ensuite de créer rapidement une nouvelle base de données avec la commande sqlite3 qcm.db < dbSQLite.dump.

Lecture d’une question

La récupération des questions se fait aisément à partir des fonctions de la bibliothèque sqlite3. Nous avons déjà expliqué dans le livre comment exécuter une requête sur une base de données. Nous employons la même méthode. Par contre, nous avons rajouté un peu de code pour déterminer si le programme s’exécute dans le simulateur ou dans l’appareil.

Nous commençons par récupérer toutes les questions avec la requête

const char *requeteSQL = "select * from Question";

Le parcours des questions se fait avec la fonction sqlite3_step. Le contenu des questions est obtenu par la fonction sqlite3_column_text. La dernière étape consiste à créer l’objet Question et à le rajouter dans le tableau des questions.

-(void) getQuestions:(sqlite3_stmt *)reqCompilee {
   while(sqlite3_step(reqCompilee) == SQLITE_ROW) {
 
     NSString *Enonce = [[NSString alloc] initWithUTF8String:
                        (char*)sqlite3_column_text(reqCompilee, 1)];
 
     NSString *BonneReponse = [[NSString alloc] initWithUTF8String:
                           (char*)sqlite3_column_text(reqCompilee, 2)];
 
     NSString *mauvaise1 = [[NSString alloc] initWithUTF8String:
                           (char*)sqlite3_column_text(reqCompilee, 3)];
 
     NSString *mauvaise2 = [[NSString alloc] initWithUTF8String:
                           (char*)sqlite3_column_text(reqCompilee, 4)];
 
     Question *question = [[Question alloc] initEnonce:Enonce
                                         BonneReponse:BonneReponse
                                            mauvaise1:mauvaise1
                                            mauvaise2:mauvaise2 ];
     [tableauDeQuestions addObject:question];
     [question release];
   }
}

Ouverture de la base

Il faut distinguer l’ouverture de la base selon la cible.

Appareil

Lorsque la cible est l’appareil, il est nécessaire de vérifier si la base a déjà été créée avant de l’ouvrir.

-(void)chargeQuestions:(BOOL)avecQuestions {
 
   tableauDeQuestions = [NSMutableArray new];
 
   sqlite3 *baseDonnees;
 
   NSString *databaseName = @"qcm.db";
 
   NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
   								NSUserDomainMask,
   								YES);
   NSString *documentsDir = [documentPaths objectAtIndex:0];
   NSString *databasePath = [documentsDir stringByAppendingPathComponent:databaseName];
 
   NSFileManager *fileManager = [NSFileManager defaultManager];
 
   // Vérifier si la BDD a déjà été créée dans l'iPhone
   if(![fileManager fileExistsAtPath:databasePath]) {
 
      NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath]
      						stringByAppendingPathComponent:databaseName];
 
      [fileManager copyItemAtPath: databasePathFromApp
                           toPath: databasePath
                            error: nil];
   }
   [fileManager release];
 
   // Partie device
   if(sqlite3_open([databasePath UTF8String],
                   &baseDonnees) == SQLITE_OK) {
      const char *requeteSQL = "select * from Question";
      sqlite3_stmt *reqCompilee;
 
      if(sqlite3_prepare_v2(baseDonnees, requeteSQL, -1,
                            &reqCompilee, NULL) == SQLITE_OK) {
         [self getQuestions:reqCompilee];
      }
      sqlite3_finalize(reqCompilee);
   }
   sqlite3_close(baseDonnees);
 
   [tableauDeQuestions randomize];
 
}

Simulateur

L’accès à la base ne nécessite pas de vérifier la présence de la bd lorsque la cible est le simulateur.

-(void)chargeQuestions:(BOOL)avecQuestions {
 
   tableauDeQuestions = [NSMutableArray new];
 
   sqlite3 *baseDonnees;
 
   NSString *databaseName = @"qcm.db";
 
   NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
   								NSUserDomainMask,
   								YES);
   NSString *documentsDir = [documentPaths objectAtIndex:0];
   NSString *databasePath = [documentsDir stringByAppendingPathComponent:databaseName];
 
   if(sqlite3_open([@"../../qcm.db" UTF8String],
                   &baseDonnees) == SQLITE_OK) {
      const char *requeteSQL = "select * from Question";
      sqlite3_stmt *reqCompilee;
 
      if(sqlite3_prepare_v2(baseDonnees, requeteSQL, -1,
                            &reqCompilee, NULL) == SQLITE_OK) {
         [self getQuestions:reqCompilee];
      }
      sqlite3_finalize(reqCompilee);
   }
   sqlite3_close(baseDonnees);
 
   [tableauDeQuestions randomize];
 
}