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

Commentaires

thierry

Le samedi 20 septembre 2008 à 07:36

trés pédagogique j'attends la suite pour comprendre subversion

SigmaCode

Le samedi 28 mars 2015 à 15:48

Encore merci pour cette deuxième partie :)

Si tu as un jour le temps pour expliquer le contenue du fichier AppDelegate cela serait merveilleux :)

Je lis à droit, à gauche, mais les explications sont vague.... et je ne sais pas quoi faire de ce fichier !

Ajoutez un commentaire

Les commentaires sont destinés à répondre en public à l'article visé et à ses commentaires déjà publiés. C'est pourquoi seul un pseudonyme vous est demandé ici. Si vous souhaitez échanger avec moi en privé, merci de me laisser un message via le menu contact en haut de la page.

Archive

Rubriques

Abonnement