Accueil > Programmation Cocoa > Le système de gestion des préférences de Mac OS X (et plus encore)

Le système de gestion des préférences de Mac OS X (et plus encore)

Par Mike Beam, le 24/08/2001

traduit par Thierry, le 16/06/2002

Petit à petit nous élargissons notre connaissance de Cocoa et en ajoutons sans cesse à notre répertoire d’outils de programmation. Aujourd’hui, nous allons poursuivre dans la continuité de l’article précédent en améliorant notre application de carnet d’adresses.

Cette application est un carnet d’adresses seulement en vertue des noms que nous avons donnés aux champs de données et aux colonnes du tableau. La logique de l’application, ses structures de données et sa GUI sont communs à une classe entière d’applications de ce type, et vous pouvez et serez capable de vous servir de pas mal des éléments de cette application dans vos logiciels. Dans cet esprit, poursuivons notre route.

Aujourd’hui, nous allons régler quelques détails de l’article précédent. Cela aura pour effet d’engager une discussion sur les différentes manières de sauvegarder les données du carnet d’adresse entre différents lancements, puis de nous amener à toucher du doigt quelques notions de gestion mémoire. Dans ce chapitre, nous allons aussi apprendre le système des préférences de Mac OS X.

Sauvegarde des données à partir des tableaux et des dictionnaires

Rappelez vous qu’à la fin de l’article précédent, notre carnet d’adresses n’avait aucun moyen de sauvegarder et de récupérer les données entre chaque lancement - un sérieux manque pour une application de ce genre. Nous aurions pu combler ce manque en construisant une application de gestion de document dès le départ et en allant au delà des méthodes de sauvegarde et de chargement de fichier que nous avons apprises.

Mais nous allons prendre une route différente. Au lieu de forcer l’utilisateur à ouvrir et sauvegarder les fichiers du carnet d’adresses à chaque session, nous allons faire en sorte que l’application charge automatiquement les données à chaque lancement et en sauvegarde les changements à chaque fois.

Chargement des données

La première chose à faire pour mettre en place une gestion automatique de sauvegarde et de chargement des données est de changer la manière dont est initialisée notre structure de données (« records » par exemple) dans awakeFromNib. Quand nous avons terminé AddressBook à la fin de l’article précédent nous avions dans awakeFromNib la simple ligne de code suivante :

- (void)awakeFromNib{records = [[NSMutableArray alloc] init];}

Cette ligne initialisait simplement la structure « records » sous forme de tableau transformable vide. La classe NSMutableArray contient d’autres méthodes init… (héritées de la classe NSArray) qui nous permettent d’initialiser un tableau, nouvellement affecté en mémoire, avec le contenu provenant d’un fichier texte, ce qui est exactement ce que nous voulons faire.

Bien sûr, le fichier texte à partir duquel nous initialisons notre structure « records » doit être formaté d’une façon particulière, en s’appuyant sur un type de document XML appelé liste de propriétés. Les méthodes NSArray que nous utiliserons pour sauvegarder le contenu de la structure « records » sur le disque se chargeront à votre place des détails de formattage. Néanmoins, dans un moment nous dirons quelques mots à propos des listes de propriétés.

La méthode que nous allons utiliser pour charger les données précédemment stockées est appelée initWithContentsOfFile:. Elle prend comme argument un objet NSString qui représente le chemin d’accès du fichier avec lequel nous souhaitons que le récepteur soit initialisé. Modifions donc notre code d’allocation mémoire et d’initialisation de la manière suivante, en remplaçant simplement le init par un initWithContentsOfFile :

- (void)awakeFromNib { records = [[NSMutableArray alloc] initWithContentsOfFile:recordsFile]; }

La variable argument recordsFile est à ce niveau encore indéfinie, mais elle sera la chaîne qui contiendra le chemin d’accès à notre fichier de données. Allons maintenant dans le fichier interface, Controller.h, et ajoutons une déclaration de variable d’instance pour recordsFile de manière à ce que nous puissions accéder au chemin à partir de n’importe quelle méthode de Controller. Voici la ligne qui se doit d’être ajoutée au bloc de déclaration des variables d’instance de Controller.h:

NSString *recordsFile;

Maintenant, nous devons initialiser recordsFile avec le chemin d’accès de notre fichier de données. Ceci est aussi effectué dans awakeFromNib, juste avant la ligne qui initialise la structure « records ». Voici awakeFromNib après avoir ajouté le code de mise en place de recordsfile :

- (void)awakeFromNib {
  recordsFile = [NSString stringWithString:@"~/Library/Preferences/AddressBookData.plist"];
  recordsFile = [recordsFile stringByExpandingTildeInPath];
  [recordsFile retain];
  records = [[NSMutableArray alloc] initWithContentsOfFile:recordsFile];
}

Dans la première ligne, nous initialisons recordsFile avec la chaîne @”~/Library/Preferences/AddressBookData.plist” en utilisant le constructeur de commodité stringWithString:. En se servant d’un tilde (~) dans le chemin, nous paramétrons effectivement notre application pour qu’elle stocke différents fichiers de données pour chaque utilisateur.

Un constructeur de commodité est utilisé ici à la place de la syntaxe plus directe @”…” de façon à ce que la première chaîne pointée par recordsFile soit autodétruite plutôt que de rester éternellement en mémoire. Dans la ligne qui suit, nous réaffectons immédiatement à recordsFile l’objet retourné par stringByExpandingTildeInPath, avec la chaîne complète que nous voulons utilisée.

Souvenez vous que ces méthodes qui font appel à des chemins d’accès ne savent pas comment gérer des chemins relatifs à un tilde, et que nous devons par conséquent invoquer cette dernière méthode pour transformer le chemin relatif en un chemin absolu contenant le bon répertoire de départ de l’utilisateur.

A ce point, nous aurions deux objets de type chaîne flottant dans l’air. Celui avec le tilde est quelque part en mémoire où nous ne pouvons plus l’atteindre car nous l’avons affecté à recordsFile, mais cela n’a pas d’importance puisqu’il a été créé avec une faculté à l’autodestruction. L’autre est le chemin absolu que nous avons créé et il est aussi autodestructif (avec l’hypothèse que les méthodes retournent les objets réglés pour l’autodestruction), mais cela ne va pas puisque nous avons encore besoin de cette chaîne. Nous remédions facilement à cette situation en envoyant un message retain à recordsFile et nous voilà sauvés.

L’implémentation de awakeFromNib que nous venons juste d’écrire comporte une défaillance peu banale car elle suppose que recordsFile indique un fichier qui existe et qui peut être utilisé pour initialiser une rangée. Que se passerait il si le fichier n’existait pas dans le cas où l’utilisateur n’aurait pas encore lancé l’application ou aurait supprimé le fichier ?

Chargement des données (suite)

Si le tableau ne subit pas une allocation mémoire et n’est pas initialisé, l’application va se planter au premier envoi de message vers la structure « records ». Ceci est clairement un comportement indésirable. Pour éviter que cela n’arrive nous devons introduire du code, afin d’éviter ceci, à la phase d’initialisation de la structure « records ».

Notre plan de contingence repose sur la valeur retournée par initWithContentsOfFile:. En fait, si le fichier indiqué dans recordsFile n’existe pas ou ne peut être proprement analysé et chargé alors l’objet « nil » est retourné. Nous pouvons alors tester que records n’est pas vide et, dans l’hypothèse où initWithContentsOfFile: échouerait, l’initialiser avec un tableau vide. Voici ce à quoi ressemble maintenant le code :

- (void)awakeFromNib {
recordsFile = @"~/Library/Preferences/AddressBookData.plist";
recordsFile = [recordsFile stringByExpandingTildeInPath];
[recordsFile retain];
records = [[NSMutableArray alloc] initWithContentsOfFile:recordsFile];
if ( nil == records ) { records = [[NSMutableArray alloc] init]; }
}

Remarquez comment nous avons écrit le test sous la forme nil == records, plutôt que sous la forme records == nil. Cela provient d’une astuce que j’ai reçue dans un message provenant de la liste de distribution « cocoa-dev » d’Apple (vous pouvez la rejoindre à l’adresse http://lists.apple.com/), et qui indiquait que si vous tentez de comparer un objet à « nil » sous la forme records = nil (affectation de « nil » à l’objet) au lieu de records == nil (comparaison de « nil » à l’objet) alors vous n’aurez aucune erreur de compilation puisque la première forme est parfaitement correcte (dans le cas d’une affectation volontaire !).

Toutefois, si vous avez l’intention d’écrire nil == records, mais écrivez accidentellement nil = records, alors là vous obtiendrez un avertissement du compilateur vous informant de votre erreur. Mais tout ça n’est qu’affaire de style, et je voulais quand même vous le mentionner.

Cela achève notre implémentation du chargement des données dans records à partir d’un fichier. Passons à la sauvegarde.

Sauvegarde des données

Maintenant que nous avons le moyen de charger les données précédemment sauvegardées, nous devons implémenter sa contrepartie : le moyen de sauvegarder les données. Nous allons accomplir ceci facilement avec la méthode writeToFile:atomically:. Le premier argument de cette méthode est encore recordsFile, et le second est une valeur booléenne - YES ou NO.

Si atomically prend la valeur NO, alors les données seront directement écrites dans le fichier situé à l’adresse indiqué dans le chemin d’accès. Sinon, alors la méthode écrira le contenu du récepteur du message dans un fichier temporaire et, seulement au moment où il déterminera que l’écriture s’est déroulée avec succès, remplacera le fichier actuel par le fichier temporaire. Cette manière de faire évitera que le fichier de données ne soit corrompu par une éventuelle défaillance ou plantage système, elle est à suivre par conséquent. Avec les lignes suivantes nous pouvons sauvegarder le contenu de records dans un fichier situé à recordsFile :

[records writeToFile:recordsFile atomically:YES];

Il serait idéal de sauvegarder le contenu de records sur le disque après chaque modification. De ce fait, nous n’aurions pas à nous soucier de la perte de données si quelque chose d’inopiné arrivait. Nous souhaiterions donc invoquer writeToFile:atomically: dans la méthode addRecord:, deleteRecord:, et insertRecord: (si vous avez implémenté la méthode tableView:setObjectValue:forTableColumn:row: de la classe NSTableDataSource que j’ai mentionné dans l’article précédent, alors vous devrez invoquer la méthode writeToFile:atomically: ici aussi).

Au lieu d’éparpiller cette ligne dans toutes les méthodes qui modifient records, nous allons créé une nouvelle méthode dans le Contrôleur que nous appèlerons saveData, et ferons appel à saveData dans chacune des méthodes sus-mentionnées. La définition de saveData est la suivante :

- (void)saveData { [records writeToFile:recordsFile atomically:YES]; }

Cette méthode a comme avantage de rendre notre code beaucoup plus facilement maintenable. Si nous décidons de modifier la manière de sauvegarder nos données, nous aurons à le faire qu’à un seul endroit.

Maintenant, tout ce qu’il nous reste à faire est d’ajouter la ligne de code [self saveData] à chacune des méthodes deleteRecord, addRecord et insertRecord juste à l’endroit où nous effectuons le [tableView reloadData]. La méthode addRecord, par exemple, va maintenant ressembler à ça :

- (IBAction)addRecord:(id)sender {
[records addObject:[self createRecord]];
[tableView reloadData];
[self saveData]; 
}

Avec ces simples morceaux de code en place, votre application est maintenant capable de sauvegarder et de charger le même package de données entre différentes sessions. Maintenant, parlons un peu des documents de listes de propriétés, et nous enchaînerons avec les préférences système de Mac OS X.

Listes de Propriétés

Une liste de propriétés est un document de type XML (« eXtensible Markup Language ») qui est utilisé intensivement par Mac OS X pour stocker des informations de configuration logiciel telles que les paramètres utilisateurs par défaut. Le contenu d’une liste de propriétés est une représentation textuelle XML structurée des données mémoire des objets actifs des classes telles que NSDictionary, NSString et NSArray. Les listes de propriétés sont donc capables de préserver la structure et le contenu de notre structure de données au niveau tableau, dictionnaire et chaîne. Je ne vais pas entamer ici une discussion sur XML car le sujet est beaucoup trop éténdu pour cet article. Pour un bon résumé de XML, jetez un oeil à XML in 10 points du W3C ou visitez XML.com d’O'Reilly.

Paramètres utilisateur par défaut

Comme introduction à notre prochain sujet de discussion nous allons parler d’une autre manière de sauvegarder notre carnet d’adresses en utilisant le système de préférences de Mac OS X. En tant qu’utilisateur maintenant averti, vous savez maintenant que Mac OS X est un système multi-utilisateur en son coeur, à contrario de Mac OS 9. De ce fait, chaque utilisateur à son propre ensemble de préférences pour chaque application qu’il utilise. Cocoa rend très facile les accès aux préférences ainsi que leur gestion par le biais de la classe NSUserDefaults.

NSUserDefaults fournit une interface de programmation au système de préférences de Mac OS X. Les paramètres par défaut sont stockés dans une base de données des préférences utilisateur sous forme de paires clé-valeur dans un dictionnaire de données. Ainsi, manipuler des préférences n’est fondamentalement pas différent de ce que l’on a appris à propos des dictionnaires (NSDictionary). Les valeurs de préférence sont stockées en utilisant la méthode setObject:forKey: (parmi d’autres) de la classe NSUserDefaults, et vous pouvez accéder à ces préférences par la suite en utilisant la méthode objectForKey:.

Pour créer un objet NSUserDefaults, vous émettez un message standardUserDefaults à l’objet, message qui retourne ainsi l’objet dont nous nous servirons pour interagir avec la base de données de préférences en relation avec l’application en cours d’utilisation. Modifions notre code de façon à ce que le tableau « records » soit initialiséà partir des données contenues dans la base des paramètres par défaut de l’utilisateur plutôt qu’à partir d’un fichier arbitraire. Pour ce faire, nous devons d’abord déclarer une variable d’instance de la classe NSUserDefaults dans Controller.h:

NSUserDefaults *prefs;

Assez simple. La prochaine étape consiste à retourner dans awakeFromNib et faire quelque chose d’analogue à ce que nous avions fait avant lors de l’initialisation à partir d’un fichier. Nous devons tout d’abord initialiser « prefs » avec l’objet paramètres par défaut en se servant de la méthode standardUserDefaults (en s’assurant que nous la retenons en mémoire pour une prochaine utilisation):

prefs = [[NSUserDefaults standardUserDefaults] retain];

Puis nous récupérons le tableau stocké dans prefs en utilisant objectForKey:. La clé à utiliser peut être n’importe quelle chaîne, nous allons donc utiliser ici la chaîne @”Addresses”. Donc, nous envoyons à prefs le message objectForKey:@”Addresses” et nous servons de la valeur retournée comme argument pour initWithArray: (une autre méthode d’initialisation de la classe NSMutableArray), et ainsi nous avons un tableau stocké et prêtà l’emploi :

records = [[NSMutableArray alloc] initWithArray:[prefs objectForKey:@"Addresses"]];

Nous avons encore la possibilité qu’il n’y ait aucune valeur assignée à la clé @”Addresses”, dans ce cas objectForKey: retourne « nil ». Cette situation se gère un peu différemment qu’avant. Quand nous avons utilisé initWithContentsOfFile:, la méthode a retourné « nil » dans le cas où le fichier ne pouvait être chargé, ce qui fournissait une méthode convenable pour tester la bonne initialisation de notre tableau. Cette fois nous utilisons la méthode objectForKey de la classe NSUserDefaults en conjonction avec l’initialiseur initWithArray: de la classe NSArray pour initialiser records. Si objectForKey ne peut trouver une valeur pour la clé spécifiée, « nil » est retourné. L’utilisation de « nil » comme argument pour initWithArray: va initialiser records en tant que rangée vide, au lieu de retourner « nil ».

Donc, nous ne pouvons effectuer le test nil == records. Nous allons plutôt inverser la logique de notre instruction if pour dire « s’il y a une clé @” Addresses”, initialise records à partir de cette préférence, sinon initialise records en utilisant init ». Pour faire ce test nous utilisons la même méthode objectForKey:, mais nous ne serons pas intéressés par le stockage de la valeur retournée, nous serons intéressés par voir si une valeur est effectivement retournée. Dans le code, cela donne ceci :

- (void)awakeFromNib  {
    prefs = [[NSUserDefaults standardUserDefaults] retain];
    if ( [prefs objectForKey:@"Adresses"] != nil )  {
        records = [[NSMutableArray alloc] initWithArray:[prefs objectForKey:@"Addresses"]];
    } else {
        records = [[NSMutableArray alloc] init];
    }
}

En général, pour récupérer une valeur par défaut à partir de prefs, tout ce que nous avons à faire est de lui envoyer un message objectForKey: en passant la clé correspondant à la préférence particulière que l’on veut obtenir. Cependant, la classe NSUserDefaults définit plusieurs autres méthodes telles que arrayForKey:, boolForKey:, integerForKey:, et bien d’autres qui nous permettent d’être plus précis dans le type de donnée retournée attendu.

La différence clé pour objectForKey: tient dans le fait que cette méthode retournera un objet vide « nil » si la clé spécifiée n’existe pas dans la base de données des valeurs par défaut. arrayForKey:, à l’inverse, va un peu plus loin et retourne un objet vide si l’objet à retourner est autre chose qu’une rangée. Ceci est aussi vrai pour les autres méthodes d’accès de la classe NSUserDefaults et leur types respectifs de données retournées.

Maintenant que nous tenons pour sûr que l’objet qui va nous être retourné sera un tableau, nous pouvons nous servir de arrayForKey: à la place de objectForKey:. Cela changera le code de la troisième ligne de awakeFromNib:

records = [[NSMutableArray alloc] initWithArray:[prefs arrayForKey:@"Addresses"]];

L’étape suivante consiste à modifier saveData de manière à prendre en compte le système des valeurs par défaut. Pour stocker les préférences dans la base de données nous utilisons la méthode setObject: forKey: de la classe NSUserDefaults. Nous allons ainsi nous débarrasser de la ligne de saveData :[records writeToFile:recordsFile atomically:YES] et la remplacer avec ceci :

 - (void)saveData { [prefs setObject:records forKey:@"Addresses"]; }

Une dernière chose à faire consiste à changer l’identifiant du package (« bundle ») de notre application, qui représente ce que NSUserDefaults utilise pour identifier le fichier dans lequel des préférences utilisateurs sont stockées. Pour régler ce nom cliquez sur l’onglet « Targets » dans Project Builder et cliquez sur la cible « AddressBook ». Au moment où le contenu de la fenêtre d’édition change avec les options de la cible, cliquez sur l’onglet « Applications ». Dans cette vue, sous « Basic Information », vous verrez le champ « Identifier ». C’est à cet endroit que vous saisirez le nom de l’identifiant du package. (NdT : Avec Xcode, allez dans « Targets », puis faites un clic-droit sur la cible « AddressBook » et choisissez « Get Info  ». Une fenêtre s’ouvre alors avec plusieurs onglets dont « Properties », c’est là que la donnée « Identifier » peut être personnalisée.)

Ce nom peut être n’importe quoi. De manière standard, cependant, on applique le mode Java d’attribution des noms de package afin d’éviter d’éventuels conflits avec d’autres applications. Pour ceux qui ne serait pas familier avec ce mode, il consiste basiquement en une suite de mots, séparés par des points, relatifs au nom de la compagnie et au nom de l’application, à l’inverse d’une adresse internet. Par exemple, les préférences du Dock sont stockées sous l’appellation com.apple.dock, celles du Terminal le sont sous com.apple.terminal, et ainsi de suite. Notre application suivra cette convention et prendra le nom com.YourName/Company.AddressBook. Ma version utilise comme identifiant com.mikebeam.AddressBook.

Maintenant, quand les préférences sont stockées sur le disque, elles sont écrites dans le répertoire Library/Preferences/ de l’utilisateur avec comme appellation celle de l’identifiant du package et avec l’extension plist. Ainsi, mes préférences pour AddressBook seront localisées dans le fichier : /Users/mike/Library/Preferences/com.mikebeam.AddressBook.plist.

Apple offre un outil pratique pour voir et éditer le contenu des documents « Liste de propriétés » (« plist ») : PropertyListEditor, que l’on peut trouver dans /Developer/Applications. Il serait instructif d’ouvrir le fichier créé par AddressBook dans les différentes implémentations montrées dans cet article et de voir comment les listes de propriétés sont structurées. Si vous voulez jeter un oeil aux balises ineptes XML, vous pouvez ouvrir des fichiers « plist » dans un éditeur de texte et vous apercevoir du détail réel de la structure de ces fichiers.

dealloc

Avant de nous quitter nous allons combler un autre manque de AddressBook en implémentant la méthode dealloc. Cette méthode est définie dans la classe NSObject et ce message est émis aux classes qui sont sur le point d’être désactivée, ou privées de leur allocation mémoire - de manière basique son but est en opposition avec celui de alloc.

Nous imposons dealloc dans nos classes pour les préparer à être désactivées. Ce que nous faisons communément avec dealloc est de désactiver chaque variable d’instance d’objet que nous avons affectées en mémoire (puis possédées). Par exemple, dans Controller nous avons les variables d’instances records, recordsFile et prefs. Par conséquent, dans notre implémentation de dealloc nous pourrions envoyer des messages de désactivation à chacun de ces objets pour neutraliser chaque alloc ou chaque message de type « retain » que nous aurions pu leur envoyer précédemment.

Il est donc de notre responsabilité de libérer la mémoire qu’ils occupent quand ils n’ont plus d’intérêt, ce qui est sûrement le cas au moment où l’objet qui les référence est détruit. Voici l’ajout de notre dealloc dans Controller.m :

 - (void)dealloc {
    [self saveData];     // just pour être sûr que
                         // les dernières données seront sauvegardées
    [prefs synchronize];
    [prefs release];
    [records release];
    [recordsFile release];
    recordsFile = nil;
    records = nil;
    prefs = nil;
    [super dealloc];  // passe la balle à la superclasse de Controller
                      // pour qu'elle puisse faire sa propre désallocation
}

Ce que fait synchronize fait est de forcer le système à synchroniser les informations relatives aux valeurs par défaut contenues en mémoire avec celles stockées sur disque. Normallement, nous n’avons pas à le faire puisque le système le fait automatiquement assez souvent. Cependant, il y a un moment où nous devons forcer cette synchronisation : juste avant la fin de l’application, car le système n’aura pas l’opportunité de le faire à ce moment précis. C’est ce que nous faisons donc ici.

Remarquez que nous n’avons rien fait des variables d’instances de prise (« outlet »). C’est parce que nous ne possédons pas ces objets. Nous n’avons pas déclaré notre emprise sur elles en utilisant alloc ou retain, ne nous en soucions donc pas.

Vous pensez peut être que dealloc est redondant dans Controller du fait que la mémoire occupée par tous les objets que nous avons désactivés est libérée quand l’application se termine. Poutant, dites vous que vous pourriez utiliser la classe Controller à l’intérieur d’une autre application. Cette autre application pourrait ne pas lancer le Contrôleur au moment où elle serait activée, et l’application pourrait décider de désactiver le Contrôleur à un moment quelconque avant qu’elle ne s’arrête.

Dans ce cas, il ne serait pas redondant d’avoir une méthode dealloc, qui codée correctement la première fois, vous permettra de gagner du temps de travail plus tard. Par ailleurs, il n’est pas si difficile d’implémenter dealloc comme nous l’avons fait ici. Donc, la morale de l’histoire est qu’il faudra imposer la méthode dealloc à chaque fois que vous aurez des objets, référencés par des instances de variables, à nettoyer.

Fin

Juste un mot sur les deux implémentations de sauvegarde de données que nous avons abordées aujourd’hui. De manière spécifique, je peux me poser la question suivante : « Quelle est la meilleure ? Un fichier séparé, ou stocker les données dans la base de données des paramètres utilisateur par défaut ? ».

La réponse à cette question tient dans le fait que les préférences utilisateurs sont supposées être des données non critiques. En fait, ce sont des données qui ne manquent pas beaucoup si elles sont bêtement corrompues ou perdues. Dans notre situation, il semble plus opportun de stocker les données du carnet d’adresse dans un fichier séparé car vous ne souhaiterez pas perdre ces données.

Mais je devine qu’un fichier n’est pas plus fiable qu’un autre, donc de ce fait la solution consiste peut être à se servir des deux, en utilisant les valeurs par défaut utilisateur comme entrepôt primaire de stockage et en utilisant un fichier séparé en back up. Dans awakeFromNib, si l’initialisation de « records » à partir de prefs échoue, alors nous pouvons utiliser le fichier back up ; et si cela échoue aussi, alors nous initialiserons « records » à vide. Quelque chose comme ça :

 if ( [prefs arrayForKey:@"Addresses"] != nil ) {
    records = [[NSMutableArray alloc] initWithArray:[prefs arrayForKey:@"Addresses"]];
} else {
    records = [[NSMutableArray alloc] initWithContentsOfFile:recordsFile];
    if ( nil == records ) records = [[NSMutableArray alloc] init];
}

Le dossier projet de cet article peut être téléchargé ici. Cette version téléchargeable du carnet d’adresse est réglée pour stocker les données à la fois dans les valeurs par défaut utilisateur et dans un fichier séparé. Additionnellement, j’ai montré comment mettre en place les méthodes relatives aux sources de données des tableaux qui vous permettront de modifier directement les données contenues dans les cellules, méthodes que j’ai brièvement mentionnées dans cet article.

Avec les bases de l’utilisation de la classe NSUserDefaults dans la poche, nous allons pouvoir explorer plus en profondeur le système des préférences, et aussi apprendre comment travailler avec plusieurs fenêtres dans une application (une fenêtre Préférences par exemple). A la prochaine fois !


Textes originaux en anglais sur O’Reilly : http://www.macdevcenter.com/pub/au/159

Thierry Programmation Cocoa , , ,

  1. Pas encore de commentaire
  1. Pas encore de trackbacks
Vous devez être identifié pour poster un commentaire