Mouviciel

Création d'applications pour Mac OS X

Les coulisses d'un développeur Mac

Délégation en Cocoa, être et avoir — Deuxième partie

Publié le dimanche 14 septembre 2008, 2 commentaires

Si le plus souvent on s'intéresse à enrichir le comportement d'un objet existant par le biais d'un autre objet appelé délégué (c'était le sujet de la première partie), il peut parfois être utile de créer une classe qui propose qu'un autre objet enrichisse son comportement. Ce deuxième article sur la délégation explique précisément cela : Comment faire pour avoir un délégué.

Avoir un objet délégué

Comme souvent, la mise en œuvre s'articule en deux étapes :

Pour illustrer la première étape, observons la classe NSWindow. Cette classe peut avoir un objet délégué : nous lui en avons donné un pour contrôler les dimensions d'une fenêtre. Pour indiquer à la fenêtre que son objet délégué était notre objet windowController, nous avons utilisé le mécanisme d'Outlet d'Interface Builder.

Il se trouve que NSWindow dispose aussi de deux méthodes relatives à la délégation : il s'agit de -delegate qui renvoie l'objet délégué de la fenêtre, et de -setDelegate: qui permet d'assigner un objet délégué à la fenêtre. Il existe aussi une variable _delegate qui contient la référence à l'objet délégué.

Ainsi, pour créer une classe qui offre la possibilité de sous-traiter certaines tâches à un objet délégué, la première étape consiste à inclure les déclarations suivantes :

@interface ClasseAvecDelegue: NSObject
{
    (id)delegate;                // L'objet délégué
}
-(id)delegate;                   // Renvoie l'objet délégué
-(void)setDelegate:(id)anObject; // Assigne un objet délégué
@end

La deuxième étape est plus intéressante. Un des principes de la délégation est que l'objet délégué n'a aucune obligation de répondre à tous les messages de délégation que pourrait lui envoyer son délégateur. Dans notre exemple de la première partie, notre objet windowController ne savait répondre qu'à une seule méthode de délégation, alors que NSWindow en propose près d'une cinquantaine en comptant celles liées aux notifications. Pourtant notre petite application fonctionnait parfaitement.

Je vous propose de faire évoluer notre petite application de façon à vous montrer la délégation du point de vue de la NSWindow.

Voici la mission à accomplir : Dans mon souci de contrôler les dimensions de la fenêtre, j'ai besoin de tenir compte de la présence ou non de la barre d'outils (ouvrez une fenêtre du Finder ou de l'Aperçu et cliquez sur l'espèce de gros grain de riz en haut à droite de la fenêtre pour voir comment la barre d'outils peut influencer la dimension de la fenêtre). Hélas ! NSWindow ne dispose d'aucun moyen d'informer son délégué que la barre d'outils vient de s'afficher ou est sur le point d'être masquée.

Nous allons donc créer une sous-classe de NSWindow qui offre des méthodes déléguées supplémentaires autour de l'affichage de la barre d'outils.

Pour commencer, ouvrez dans Xcode le projet Delegate que nous avons créé lors de la première partie. Ajoutez une classe au projet : Menu File—New File..., puis Objective-C class. Nommez-la MCLWindow.m et choisissez de créer aussi MCLWindow.h. Il s'agit de la sous-classe de NSWindow à laquelle nous allons ajouter de nouvelles méthodes de délégation relatives à la barre d'outils. J'ai choisi le préfixe MCL (MouviCieL) pour distinguer mes propres classes de celles venant d'autres sources et que je pourrais utiliser dans mes projets.

Modifiez l'interface MCLWindow.h de façon à en faire une sous-classe de NSWindow :

//
//  MCLWindow.h
//  Delegate
//
//  Created by mouviciel on 13/09/08.
//  Copyright 2008 Mouviciel. All rights reserved.
//

#import <Cocoa/Cocoa.h>


@interface MCLWindow : NSWindow {

}

@end

Ensuite, ouvrez le fichier qui décrit l'interface graphique MainMenu.xib (ou MainMenu.nib). Nous allons déclarer que la fenêtre est un objet de la classe MCLWindow au lieu de NSWindow. Assurez-vous que les palettes Library et Inspector sont ouvertes (resp. cmd-shift-L et cmd-shift-I).

Dans la fenêtre MainMenu.xib (English), sélectionnez l'objet Window (Window), puis dans la palette Inspector choisissez l'onglet Identity. Dans la rubrique Class Identity remplacez la classe NSWindow par la classe MCLWindow.

Pour que notre exercice ait un sens, vous allez ajouter une barre d'outils à la fenêtre. Dans la palette Library trouvez l'élément Toolbar de classe NSToolbar et faites-le glisser sur la fenêtre de l'application. Sauvez MainMenu.xib (cmd-S) et revenez à Xcode.

Maintenant, la fenêtre de l'application Delegate est une MCLWindow qui a le même comportement que NSWindow mais qui est entièrement sous notre contrôle. Si vous compilez et lancez (Build and Go), vous observerez le même comportement que lors de la première partie, à savoir des traces dans la console à chaque redimensionnement de la fenêtre.

Pour être informés de l'apparition et de la disparition de la barre d'outils, nous allons détourner la méthode -toggleToolbarShown: qui est reçue par la fenêtre chaque fois que l'utilisateur demande d'afficher ou de masquer la barre d'outils. Cela se passe dans le fichier MCLWindow.m. Voici la première étape de nos modifications :

//
//  MCLWindow.m
//  Delegate
//
//  Created by mouviciel on 13/09/08.
//  Copyright 2008 Mouviciel. All rights reserved.
//

#import "MCLWindow.h"


@implementation MCLWindow

-(void)toggleToolbarShown:(id)sender
{
    [super toggleToolbarShown:sender];
    
    if ([[self toolbar] isVisible])
    {
        NSLog(@"Barre d'outils visible");
    }
    else
    {
        NSLog(@"Barre d'outils masquée");
    }
}

@end

Sauvez (cmd-S), compilez et lancez (Build and Go). Vous devriez voir une fenêtre avec une barre d'outils. Si vous redimensionnez la fenêtre, des traces apparaissent dans la console, comme lors de la première partie. Si vous masquez la barre d'outils une trace dans la console indique cela, de même si vous l'affichez :

[Session started at 2008-09-13 15:38:21 +0200.]
2008-09-13 15:38:23.854 Delegate[4580:10b] Barre d'outils masquée
2008-09-13 15:38:24.328 Delegate[4580:10b] Barre d'outils visible

Maintenant que les bases sont posées, nous pouvons commencer à offrir des méthodes déléguées.

Au lieu d'informer le développeur via des traces dans la console, je vous propose de faire en sorte que l'objet délégué soit informé que la barre d'outils vient d'être affichée ou masquée par l'intermédiaire de deux méthodes de délégation, respectivement -toolbarDidShow: et -toolbarDidHide:.

La technique pour envoyer une méthode à un objet seulement s'il est capable d'y répondre est la méthode -respondsToSelector:. Ainsi, le code de MCLWindow.m devient :

    if ([[self toolbar] isVisible])
    {
        if ([[self delegate]
             respondsToSelector:@selector(toolbarDidShow:)])
        {
            [[self delegate] toolbarDidShow:self];
        }
    }
    else
    {
        if ([[self delegate]
             respondsToSelector:@selector(toolbarDidHide:)])
        {
            [[self delegate] toolbarDidHide:self];
        }
    }

Ajoutons la prise en compte de ces deux méthodes par notre objet délégué de classe windowController (dans le fichier windowController.m) :

@implementation windowController

- (void)toolbarDidShow:(id)sender
{
    NSLog(@"Barre d'outils affichée");
}

- (void)toolbarDidHide:(id)sender
{
    NSLog(@"Barre d'outils cachée");
}

- (NSSize)windowWillResize:(NSWindow *)window

Sauvez (cmd-S), compilez et lancez (Build and Go). Masquez, puis affichez la barre d'outils. Voici ce que vous devriez voir dans la console :

[Session started at 2008-09-13 16:09:39 +0200.]
2008-09-13 16:09:42.265 Delegate[4687:10b] Barre d'outils cachée
2008-09-13 16:09:44.150 Delegate[4687:10b] Barre d'outils affichée

Bravo ! Vous venez d'ajouter deux méthodes de délégation à une classe et à faire en sorte que l'objet délégué recoive ces messages.

Cependant, il reste une dernière petite chose. Si vous sélectionnez la rubrique Errors and Warnings de la barre latérale Groups & Files du projet Delegate, vous observez deux warnings : warning: no '-toolbarDidShow:' method found et warning: no '-toolbarDidHide:' method found. C'est normal puisque le compilateur n'a pas été informé de leur existence.

Ce ne sont pas des méthodes de la classe MCLWindow, donc il n'est pas possible de les déclarer dans son interface. Pourtant les déclarer ailleurs n'aurait pas de sens. Heureusement, Objective-C offre un mécanisme parfaitement adapté pour décrire une interface sans offrir d'implémentation. Il s'agit de la notion de protocole. Et depuis la deuxième version, il est possible de spécifier que certains éléments de l'interface sont optionnels. Voici donc la déclaration de nos méthodes de délégation (à la fin de MCLWindow.h) :

@interface MCLWindow : NSWindow {

}

@end

@protocol MCLWindowDelegate
@optional
-(void)toolbarDidShow:(id)sender;
-(void)toolbarDidHide:(id)sender;
@end

Sauvez (cmd-S), compilez et lancez (Build and Go). Voilà ! Tout fonctionne correctement maintenant. Je vous laisse le soin d'ajouter les méthodes déléguées -toolbarWillShow: et toolbarWillHide:.

Vous savez maintenant faire d'un objet un délégué d'un autre objet et faire qu'un objet laisse un autre objet agir à sa place. J'espère que vous trouverez des applications à ces deux articles sur la délégation en Cocoa

Délégation en Cocoa, être et avoir — Première partie

Publié le jeudi 4 septembre 2008, 6 commentaires

De nombreuses classes en Cocoa ont la possibilité de modifier leur comportement grâce au mécanisme de la délégation : Une instance d'une telle classe peut accueillir un objet qui souhaite être son délégué. Elle lui envoie alors certains messages, nommés méthodes déléguées, que cet objet sera libre de traiter.

Ainsi il y a deux facettes à la délégation :

Ce deuxième point sera le sujet de la deuxième partie.

Être un objet délégué

La plupart du temps, le développeur s'intéresse à enrichir ou à personnaliser le comportement d'un objet qui accepte d'avoir un objet délégué. Par exemple, il souhaitera que la fenêtre principale de son application soit redimensionnée selon des règles qu'il aura fixées.

Justement la classe NSWindow dispose de la méthode déléguée -windowWillResize:toSize: qui est envoyée à l'objet délégué de la fenêtre chaque fois que la fenêtre s'apprête à être redimensionnée. Il suffit que l'objet délégué réponde à cette méthode, et il pourra mettre son grain de sel dans le redimensionnement de la fenêtre.

Voici comment cela se passe.

Nous allons réaliser ensemble une petite application Cocoa dont l'unique fonction est de conserver les proportions de sa fenêtre lorsqu'elle est redimensionnée.

Tout d'abord, créez dans Xcode un nouveau projet d'application Cocoa. Nommez-le Delegate. Ajoutez une classe à ce projet : Menu File—New File..., puis Objective-C class. Nommez-la windowController.m et choisissez de créer aussi windowController.h. Il s'agit du contrôleur de la fenêtre, qui agira pour elle en qualité de délégué.

Ensuite, ouvrez le fichier qui décrit l'interface graphique MainMenu.xib (ou MainMenu.nib si vous n'avez pas une version récente d'Xcode). Nous allons y ajouter le contrôleur que nous venons de créer et le déclarer comme délégué de la fenêtre principale. Si ce n'est pas déjà fait, ouvrez la palette Library (cmd-shift-L) et la palette Inspector (cmd-shift-I).

Dans la palette Library trouvez l'élément Object de classe NSObject (c'est un cube bleu sans sphère autour) et faites-le glisser dans la fenêtre nommée MainMenu.xib (English). Cette fenêtre contient la description complète de l'interface graphique de l'application.

Assurez-vous que l'objet que vous venez de faire glisser est sélectionné, puis dans la palette Inspector choisissez l'onglet Identity (c'est un signe i dans un cercle). Dans la rubrique Class Identity Choisissez la classe windowController (c'est la classe que nous avonc créée, normalement elle est à la fin de la liste déroulante).

À ce stade, nous venons de créer une instance de windowController directement dans l'interface graphique. Nous allons maintenant lier cet objet à la fenêtre de l'application en le déclarant délégué de la fenêtre. Cela se passe encore dans Interface Builder

À l'intérieur de la fenêtre MainMenu.xib (English) localisez la fenêtre de l'application, elle s'appelle Window (Window). Réalisez un ctrl-drag depuis son icône jusque vers l'objet windowController (placez la souris sur l'icône de la fenêtre de l'application, pressez la touche ctrl puis le bouton de la souris, et sans relâcher déplacez la souris jusque sur l'icône de notre nouvel objet, maintenant relâchez).

Un petit menu noir semi-transparent apparaît, il est intitulé Outlets et contient un unique élément nommé delegate. Nous y voici ! Sélectionnez cet élément. Et voilà : notre windowController est devenu le délégué de la fenêtre de l'application. Sauvez MainMenu.xib (cmd-S) et revenez à Xcode.

Vérifions que cela marche comme nous le souhaitons : faisons en sorte que notre délégué accomplisse sa tâche de délégué. Modifiez le fichier windowController.m de la façon suivante (les modifications sont en gras) :

//
//  windowController.m
//  Delegate
//
//  Created by mouviciel on 04/09/08.
//  Copyright 2008 Mouviciel. All rights reserved.
//

#import "windowController.h"


@implementation windowController

- (NSSize)windowWillResize:(NSWindow *)window
                    toSize:(NSSize)proposedFrameSize
{
    NSLog(@"La taille demandée est: %@",
          [NSValue valueWithSize:proposedFrameSize]);
    return proposedFrameSize;
}

@end

Sauvez (cmd-S), compilez et lancez (Build and Go). Si la console n'apparaît pas (c'est une fenêtre avec à l'intérieur un texte semblable à : [Session started at 2008-09-04 22:16:09 +0200.]), pressez cmd-shift-R. Essayez de redimensionner la fenêtre de notre application Delegate et observez la console. Si tout se passe comme prévu, vous devriez voir une série de messages de la forme :

[Session started at 2008-09-04 22:21:31 +0200.]
2008-09-04 22:21:34.264 Delegate[20657:10b] Taille: NSSize: {482, 382}
2008-09-04 22:21:34.280 Delegate[20657:10b] Taille: NSSize: {485, 382}
2008-09-04 22:21:34.297 Delegate[20657:10b] Taille: NSSize: {491, 382}

Félicitations ! Vous venez de créer un objet délégué qui agit pour le compte d'un objet délégateur. Je vous laisse le soin de faire évoluer cette application de telle sorte que la largeur de la fenêtre soit exactement le double de sa hauteur (un indice : la taille proposedFrameSize est la taille demandée par l'utilisateur qui redimensionne la fenêtre, la valeur retournée par la méthode est la taille qui sera réellement donnée à la fenêtre).

Vous l'avez sans doute compris, la deuxième partie illustrera l'autre facette de la délégation, à savoir : avoir un objet délégué.

Contrôle de versions et Xcode - Première partie

Publié le jeudi 21 août 2008, aucun commentaire

Parmi les outils que je trouve essentiels en informatique, j'en connais au moins deux qui sont peu ou mal enseignés. Il s'agit du test et du contrôle de versions. Pour l'heure, je garde le test de côté et je vous parlerai ici du contrôle de versions. Ce premier article évoque les bénéfices qu'on peut attendre d'un outil de contrôle de version. Le prochain article détaillera les étapes à suivre pour activer un tel outil dans Xcode.

Le contrôle de versions pour quoi faire ?

Vous est-il déjà arrivé d'avoir écrit un morceau de code dont vous êtes très fier, mais qui vous laisse l'impression qu'il pourrait être amélioré ? Qu'avez-vous fait ensuite ? Vous avez peut-être poursuivi sur la lancée, modifié une ligne ici, ajouté une fonction là, revu un peu l'architecture en prévision d'autres modifications dont vous venez d'avoir l'idée... Et puis patatras, plus rien ne marche ! Bien sûr, loi de Murphy oblige, vous n'aviez pas sauvegardé votre travail initial. Il ne vous reste plus qu'à revenir en arrière petit à petit ou bien tout recommencer depuis le début.

Si vous êtes plus prudent, vous aurez sans doute sauvé votre travail régulièrement. Peut-être les premières fois en écrasant les anciennes modifications par les nouvelles, jusqu'au jour où vous vous apercevez que votre modification en cours ne marche pas, ni celle que vous avez sauvée. La version correcte était juste un peu plus ancienne... Peut-être décidez-vous alors de faire des sauvegardes successives dans des dossiers dont le nom inclut la date ou un quelconque repère unique, de façon à conserver toutes vos modifications. Dans ce dernier cas, vous n'êtes plus très loin du contrôle de versions et cet article vous intéressera certainement.

Un outil de contrôle de version fait exactement cela : conserver et retrouver l'historique des modifications du code source d'un logiciel. Il le fait de façon un peu plus élaborée qu'une collection de dossiers datés. D'abord, les versions successives ne sont pas sauvées in extenso : seules les modifications depuis la version précédente sont archivées. Cela permet de garder l'historique en utilisant une quantité de mémoire très faible.

Ensuite, l'historique n'est pas nécessairement linéraire. Il est possible de faire plusieurs évolutions parallèles à partir d'une même version antérieure. L'historique devient un arbre avec son tronc et ses branches, c'est un vocabulaire couramment utilisé parmi les outils de contrôle de versions. Quel est l'intérêt de cette pratique ?

Supposons que je sois l'auteur d'un super logiciel que j'ai baptisé fort à propos MonSuperLogiciel. La première version a déjà beaucoup de succès et j'ai commencé à développer des nouvelles fonctionnalités pour la deuxième version qui sera encore plus géniale. Et voici qu'un utilisateur découvre une erreur dans la première version. Le système de branches de l'outil de contrôle de versions me permet de corriger l'erreur de la version 1.0 et de rapidement proposer une version 1.1. Pendant ce temps je peux continuer à travailler sur la version 2.0, elle aussi issue de la version 1.0.

Enfin, un outil de contrôle de versions offre la possibilité inverse, à savoir réunir les modifications développées dans deux branches parallèles. L'analogie botanique trouve ici ses limites et le mot consacré est issu de l'anglais : l'opération est généralement appelée merge. Pour poursuivre sur mon exemple, l'intérêt du merge est que je peux intégrer la correction de l'erreur réalisée dans la version 1.1 de MonSuperLogiciel dans la branche consacrée au développement de la version 2.0.

Si vous souhaitez vous plonger un peu plus dans les arcanes du contrôle de versions, je vous invite à lire (en anglais) le dossier Source Control HOW TO sur le blog d'Eric Sink.

Quels outils de contrôle de version pour Xcode ?

De nombreux outils de contrôle de version existent. Une liste incomplète existe sur Wikipédia. Certains sont plus en vogue ces temps-ci, comme par exemple Git ou Mercurial, pour lequel un petit guide pour Mac en français existe sur le blog Cocoa.fr. Mais si vous travaillez avec Xcode votre choix est réduit à trois : CVS, Subversion et Perforce.

Dans mon prochain article vous saurez pourquoi j'ai choisi Subversion et je vous expliquerai comment l'activer pour vos projets Xcode.

Mouviciel, création d'applications pour Mac OS X

Publié le dimanche 29 juin 2008, aucun commentaire

Comme son nom le laisse entendre, ce blog a pour vocation d'échanger autour du développement de logiciel dans l'environnement Mac. Il s'inscrit dans un projet, Mouviciel, dont l'objectif est de créer et diffuser des applications pour Mac OS X.

Les sujets que je souhaite aborder ici sont bien sûr techniques, mais pas seulement. Créer une application implique vouloir que des gens l'utilisent. Comment faire connaître son logiciel, comment obtenir un accueil favorable auprès de ses utilisateurs sont des thèmes dont j'aimerais parler avec vous.

Bien sûr ces échanges auront pour points de départ des éléments de ma propre expérience. Mais je tiens à vous encourager à participer, amis lecteurs. On dit que la valeur d'un blog réside en grande partie dans ses commentaires.

Archive

Rubriques

Abonnement