Objective-C succinctement Méthodes

Dans ce chapitre, nous explorerons les méthodes Objective-C de manière beaucoup plus détaillée que dans les chapitres précédents. Cela inclut une discussion approfondie des méthodes d'instance, des méthodes de classe, des méthodes intégrées importantes, de l'héritage, des conventions de dénomination et des modèles de conception courants..


Méthodes d'instance et de classe

Nous avons travaillé avec des méthodes d'instance et de classe tout au long de ce livre, mais prenons un moment pour formaliser les deux grandes catégories de méthodes dans Objective-C:

  • Méthodes d'instance - Fonctions liées à un objet. Les méthodes d'instance sont les "verbes" associés à un objet.
  • Méthodes de classe - Fonctions liées à la classe elle-même. Ils ne peuvent pas être utilisés par des instances de la classe. Celles-ci sont similaires aux méthodes statiques en C #.

Comme nous l'avons vu à plusieurs reprises, les méthodes d'instance sont désignées par un trait d'union avant le nom de la méthode, alors que les méthodes de classe sont précédées du signe plus. Par exemple, prenons une version simplifiée de notre Personne.h fichier:

@interface Person: NSObject @property (copy) NSString * name; - (vide) dites bonjour; + (Person *) personWithName: (NSString *) name; @fin

De même, les méthodes d'implémentation correspondantes doivent également être précédées d'un trait d'union ou d'un signe plus. Donc, un minimum Personne.m pourrait ressembler à quelque chose comme:

#import "Person.h" @implementation Person @synthesize name = _name; - (void) sayHello NSLog (@ "BONJOUR");  + (Person *) personWithName: (NSString *) name Person * person = [[Person alloc] init]; personne.nom = nom; personne de retour;  @fin

le dis bonjour méthode peut être appelée par les instances du La personne classe, alors que le personWithName La méthode ne peut être appelée que par la classe elle-même:

Person * p1 = [Person personWithName: @ "Frank"]; // Méthode de classe. [p1 dit Bonjour]; // méthode d'instance.

La plupart de cela devrait vous être familier à présent, mais nous avons maintenant l'occasion de parler de certaines des conventions uniques d'Objective-C..


Le super mot-clé

Dans tout environnement orienté objet, il est important de pouvoir accéder aux méthodes de la classe parente. Objective-C utilise un schéma très similaire à C #, à la place de base, il utilise le super mot-clé. Par exemple, la mise en œuvre suivante de dis bonjour afficherait BONJOUR dans le panneau de sortie, puis appelez la version de la classe parente dis bonjour:

- (vide) sayHello NSLog (@ "BONJOUR"); [super dire bonjour]; 

Contrairement à C #, les méthodes de substitution ne doivent pas nécessairement être explicitement marquées comme telles. Vous verrez cela avec les deux init et dealloc méthodes discutées dans la section suivante. Même si ceux-ci sont définis sur le NSObject classe, le compilateur ne se plaint pas lorsque vous créez votre propre init et dealloc méthodes dans les sous-classes.


Méthodes d'initialisation

Les méthodes d'initialisation sont obligatoires pour tous les objets. Un objet nouvellement alloué n'est pas considéré comme "prêt à l'emploi" jusqu'à ce que l'une de ses méthodes d'initialisation ait été appelée. Ils sont l’endroit pour définir les valeurs par défaut pour les variables d’instance et pour définir l’état de l’objet. le NSObject classe définit une valeur par défaut init méthode qui ne fait rien, mais il est souvent utile de créer le vôtre. Par exemple, une coutume init mise en œuvre pour notre Navire classe peut affecter des valeurs par défaut à une variable d'instance appelée _munitions:

- (id) init self = [super init]; si (auto) _ammo = 1000;  retourner soi-même; 

C’est le moyen canonique de définir une coutume init méthode. le soi mot-clé est l'équivalent de C # ce-il est utilisé pour faire référence à l'instance appelant la méthode, ce qui permet à un objet de s'envoyer des messages. Comme vous pouvez le voir, tous init des méthodes sont nécessaires pour renvoyer l'instance. C’est ce qui permet d’utiliser le [[Ship alloc] init] syntaxe pour assigner l'instance à une variable. Notez également que parce que le NSObject l'interface déclare le init méthode, il n’est pas nécessaire d’ajouter un init déclaration à Ship.h.

Bien que simple init Des méthodes telles que celle présentée dans l'exemple précédent sont utiles pour définir les valeurs de variable d'instance par défaut. Il est souvent plus pratique de passer des paramètres à une méthode d'initialisation:

- (id) initWithAmmo: (unsigned int) theAmmo self = [super init]; si (auto) _ammo = theAmmo;  retourner soi-même; 

Si vous venez d'un fond C #, vous pourriez être mal à l'aise avec le initWithAmmo nom de la méthode. Vous vous attendriez probablement à voir le Munitions paramètre séparé du nom de méthode réel comme void init (uint munitions); cependant, la dénomination de la méthode Objective-C est basée sur une philosophie totalement différente.

Rappelez-vous que l'objectif d'Objective-C est de forcer une API à être aussi descriptive que possible, en veillant à ce qu'il n'y ait absolument aucune confusion quant à ce qu'un appel de méthode va faire. Vous ne pouvez pas concevoir une méthode comme une entité distincte de ses paramètres. Ils constituent une seule et même unité. Cette décision de conception est en fait reflétée dans la mise en œuvre d'Objective-C, qui ne fait aucune distinction entre une méthode et ses paramètres. En interne, un nom de méthode est en fait le liste de paramètres concaténés.

Par exemple, considérons les trois déclarations de méthode suivantes. Notez que les deuxième et troisième ne sont pas des méthodes intégrées de NSObject, alors vous faire besoin de les ajouter à l'interface de la classe avant de les implémenter.

- (id) init; - (id) initWithAmmo: (unsigned int) theAmmo; - (id) initWithAmmo: (unsigned int) Capitaine theAmmo: (Person *) theCaptain;

Bien que cela ressemble à une surcharge de méthode, ce n’est techniquement pas le cas. Ce ne sont pas des variations sur le init method-ce sont toutes des méthodes complètement indépendantes avec des noms de méthodes distincts. Les noms de ces méthodes sont les suivants:

init initWithAmmo: initWithAmmo: capitaine:

C’est la raison pour laquelle vous voyez une notation comme indexOfObjectWithOptions: passingTest: et indexOfObjectAtIndexes: options: passingTest: pour faire référence à des méthodes dans la documentation officielle Objective-C (extraite de NSArray).

D'un point de vue pratique, cela signifie que le premier paramètre de vos méthodes devrait toujours être décrit par le nom de la méthode "primaire". Les programmeurs Objective-C désapprouvent généralement les méthodes ambiguës telles que les suivantes:

- (id) shoot: (Ship *) aShip;

Au lieu de cela, vous devez utiliser une préposition pour inclure le premier paramètre dans le nom de la méthode, comme suit:

- (id) shootOtherShip: (Ship *) aShip;

Y compris les deux AutreShip et un navire dans la définition de la méthode peut sembler redondant, mais rappelez-vous que la un navire L'argument n'est utilisé qu'en interne. Quelqu'un appelant la méthode va écrire quelque chose comme shootOtherShip: découverteOne, où découverteOne est la variable contenant le vaisseau que vous voulez tirer. C’est exactement le type de verbosité que recherchent les développeurs Objective-C..

Initialisation de classe

En plus de init méthode d'initialisation les instances, Objective-C fournit également un moyen de configurer Des classes. Avant d’appeler des méthodes de classe ou d’instancier des objets, le runtime Objective-C appelle le initialiser méthode de classe de la classe en question. Cela vous donne la possibilité de définir des variables statiques avant que quiconque utilise la classe. L'un des cas d'utilisation les plus courants consiste à configurer des singletons:

statique Ship * _sharedShip; + (void) initialize if (self == [classe de navire]) _sharedShip = [[self alloc] init];  + (Ship *) sharedShip return _sharedShip; 

Avant la première fois [Envoi partagé] est appelé, le runtime appellera [Initialiser le navire], qui s'assure que le singleton est défini. Le modificateur de variable statique a le même objectif que dans C # - il crée une variable de niveau classe au lieu d’une variable d’instance. le initialiser La méthode est appelée une seule fois, mais sur toutes les super-classes. Vous devez donc veiller à ne pas initialiser les variables de niveau classe plusieurs fois. C’est pourquoi nous avons inclus le self == [Classe de navire] conditionnel pour vous assurer _shareShip est alloué seulement dans le Navire classe.

Notez également qu’à l’intérieur d’une méthode de classe, le soi mot-clé fait référence à la classe elle-même, pas à une instance. Alors, [auto-allouer] dans le dernier exemple est l'équivalent de [Allocation du navire].


Méthodes de désallocation

La contrepartie logique de la méthode d’initialisation d’une instance est la suivante: dealloc méthode. Cette méthode est appelée sur un objet lorsque le nombre de références atteint zéro et que la mémoire sous-jacente est sur le point d'être désallouée..

Deallocation in MMR

Si vous utilisez la gestion manuelle de la mémoire (non recommandé), vous devez libérer toutes les variables d'instance que votre objet a allouées dans le répertoire. dealloc méthode. Si vous ne libérez pas de variables d'instance avant que votre objet ne soit hors de portée, vous aurez des pointeurs pendants vers vos variables d'instance, ce qui signifie une fuite de mémoire chaque fois qu'une instance de la classe est publiée. Par exemple, si notre Navire classe allouée une variable appelée _pistolet dans son init méthode, vous auriez à le libérer dans dealloc. Ceci est démontré dans l'exemple suivant (Gun.h contient une interface vide qui définit simplement le Pistolet classe):

#import "Ship.h" #import "Gun.h" @implementation Ship BOOL _gunIsReady; Gun * _gun;  - (id) init self = [super init]; si (auto) _gun = [[Gun alloc] init];  retourner soi-même;  - (void) dealloc NSLog (@ "Désallocation d'un navire"); [_gun release]; [super dealloc];  @fin

Tu peux voir le dealloc méthode en action en créant un Navire et le relâchant, comme ceci:

int principal (int argc, const char * argv []) @autoreleasepool navire * navire = [[navire all] init]; [bateau autorelease]; NSLog (@ "Le navire devrait toujours exister dans autoreleasepool");  NSLog (@ "Le navire devrait être désalloué maintenant"); retourne 0; 

Cela montre également le fonctionnement des objets libérés automatiquement. le dealloc la méthode ne sera pas appelée jusqu'à la fin de la @autoreleasepool block, le code précédent doit donc afficher les éléments suivants:

Le navire devrait toujours exister dans autoreleasepool Désallocation d'un navire Le navire devrait être désalloué maintenant

Notez que le premier NSLog () message dans principale() est affiché avant celui dans le dealloc méthode, même si elle s'appelait après la autorelease appel.

Deallocation dans ARC

Toutefois, si vous utilisez le comptage automatique des références, toutes vos variables d’instance seront désallouées automatiquement. [super dealloc] sera également appelé pour vous (vous ne devez jamais l'appeler explicitement). Donc, la seule chose qui vous préoccupe, ce sont les variables non-objets telles que les tampons créés avec C malloc ().

Comme init, vous n'avez pas à mettre en place un dealloc méthode si votre objet ne nécessite aucune manipulation particulière avant sa sortie. C'est souvent le cas pour les environnements de comptage automatique de références.


Méthodes privées

Un grand obstacle pour les développeurs C # qui passent à Objective-C est le manque apparent de méthodes privées. Contrairement à C #, toutes les méthodes d'une classe Objective-C sont accessibles à des tiers. cependant, il est possible de imiter le comportement des méthodes privées.

N'oubliez pas que les clients importent uniquement l'interface d'une classe (c'est-à-dire les fichiers d'en-tête). Ils ne doivent jamais voir l'implémentation sous-jacente. Donc, en ajoutant de nouvelles méthodes à l’intérieur du la mise en oeuvre déposer sans les inclure dans le interface, nous pouvons efficacement cacher des méthodes à d'autres objets. Bien qu’il s’agisse de méthodes plus conventionnelles que de méthodes "vraies" privées, il s’agit essentiellement de la même fonctionnalité: le fait d’appeler une méthode qui n’est pas déclarée dans une interface entraîne une erreur de compilation..

Tentative d'appeler une méthode "privée"

Par exemple, supposons que vous deviez ajouter un disque privé préparerSocher méthode à la Navire classe. Tout ce que vous avez à faire est de l'omettre Ship.h en l'ajoutant à Ship.m:

// Ship.h @interface Ship: NSObject @property (faible) Person * captain; - (vide) tirer; @fin

Cela déclare une méthode publique appelée tirer, qui utilisera le privé préparerSocher méthode. L'implémentation correspondante pourrait ressembler à quelque chose comme:

// Ship.m #import "Ship.h" @implementation Ship BOOL _gunIsReady;  @synthesize captain = _captain; - (vide) shoot if (! _gunIsReady) [self prepareToShoot]; _gunIsReady = YES;  NSLog (@ "Firing!");  - (void) prepareToShoot // Exécute des fonctionnalités privées. NSLog (@ "Préparation de l'arme principale…");  @fin

A partir de Xcode 4.3, vous pouvez définir des méthodes privées nulle part dans la mise en œuvre. Si vous utilisez la méthode private avant que le compilateur l'ait vue (comme dans l'exemple précédent), le compilateur vérifie le reste du bloc d'implémentation pour la définition de la méthode. Avant Xcode 4.3, vous deviez soit définir une méthode privée. avant il a été utilisé ailleurs dans le fichier, ou en avant-déclaration avec un extension de classe.

Les extensions de classe sont un cas particulier de catégories, qui sont présentés dans le chapitre suivant. Tout comme il n’ya aucun moyen de marquer une méthode comme privée, il n’ya aucun moyen de marquer une méthode comme protégée; Cependant, comme nous le verrons dans le chapitre suivant, les catégories constituent une alternative puissante aux méthodes protégées..


Sélecteurs

Les sélecteurs sont la manière dont Objective-C représente les méthodes. Ils vous permettent de "sélectionner" dynamiquement l'une des méthodes d'un objet, qui peut être utilisée pour faire référence à une méthode au moment de l'exécution, transmettre une méthode à une autre fonction et déterminer si un objet a une méthode particulière. Pour des raisons pratiques, vous pouvez considérer un sélecteur comme un nom alternatif pour une méthode..

Représentation d'une méthode par les développeurs par rapport à la représentation par Objective-C

En interne, Objective-C utilise un numéro unique pour identifier chaque nom de méthode utilisé par votre programme. Par exemple, une méthode appelée dis bonjour pourrait se traduire par 4984331082. Cet identifiant s'appelle un sélecteur, et il est beaucoup plus efficace pour le compilateur de faire référence à des méthodes que leur représentation sous forme de chaîne complète. Il est important de comprendre qu'un sélecteur représente uniquement la méthode. prénom-pas une implémentation de méthode spécifique. En d'autres termes, un dis bonjour méthode définie par le La personne la classe a le même sélecteur qu'un dis bonjour méthode définie par le Navire classe.

Les trois principaux outils pour travailler avec les sélecteurs sont:

  • @sélecteur() - Renvoie le sélecteur associé à un nom de méthode de code source.
  • NSSelectorFromString () - Retourne le sélecteur associé à la représentation sous forme de chaîne d'un nom de méthode. Cette fonction permet de définir le nom de la méthode au moment de l’exécution, mais elle est moins efficace que @sélecteur().
  • NSStringFromSelector () - Renvoie la représentation sous forme de chaîne d'un nom de méthode à partir d'un sélecteur.

Comme vous pouvez le constater, il existe trois manières de représenter un nom de méthode dans Objective-C: en tant que code source, en tant que chaîne ou en tant que sélecteur. Ces fonctions de conversion sont illustrées dans la figure suivante:

Conversion entre code source, chaînes et sélecteurs

Les sélecteurs sont stockés dans un type de données spécial appelé SEL. L'extrait suivant illustre l'utilisation de base des trois fonctions de conversion présentées dans la figure précédente:

int principal (int argc, const char * argv []) @autoreleasepool sélecteur SEL = @selector (sayHello); NSLog (@ "% @", NSStringFromSelector (sélecteur)); if (sélecteur == NSSelectorFromString (@ "sayHello")) NSLog (@ "Les sélecteurs sont égaux!");  retourne 0; 

Tout d'abord, nous utilisons le @sélecteur() directive pour comprendre le sélecteur pour une méthode appelée dis bonjour, qui est une représentation de code source d'un nom de méthode. Notez que vous pouvez passer tout nom de la méthode à @sélecteur() -il n'est pas nécessaire qu'il existe ailleurs dans votre programme. Ensuite, nous utilisons le NSStringFromSelector () fonction pour reconvertir le sélecteur en chaîne afin que nous puissions l'afficher dans le panneau de sortie. Enfin, le conditionnel indique que les sélecteurs ont une correspondance un à un avec les noms de méthodes, que vous les trouviez via des noms de méthodes ou des chaînes codés en dur..

Noms de méthode et sélecteurs

L'exemple précédent utilise une méthode simple qui ne prend aucun paramètre, mais il est important de pouvoir passer des méthodes qui faire accepter les paramètres. Rappelez-vous qu'un nom de méthode est composé du nom de méthode principal concaténé avec tous les noms de paramètres. Par exemple, une méthode avec le Signature

- (void) sayHelloToPerson: (Person *) aPerson withGreeting: (NSString *) aGreeting;

aurait une méthode prénom de:

sayHelloToPerson: withGreeting:

C'est ce que vous passeriez à @sélecteur() ou NSSelectorFromString () pour renvoyer l'identifiant de cette méthode. Les sélecteurs fonctionnent uniquement avec la méthode des noms (pas de signatures), donc il y a ne pas une correspondance individuelle entre les sélecteurs et les signatures. En conséquence, la méthode prénom dans le dernier exemple correspondra également une signature avec différents types de données, y compris les suivantes:

- (void) sayHelloToPerson: (NSString *) aName withGreeting: (BOOL) useGreeting;

La verbosité des conventions de dénomination d'Objective-C évite les situations les plus confuses; cependant, les sélecteurs pour les méthodes à un paramètre peuvent toujours être délicats, car l’ajout de deux points au nom de la méthode le change en un complètement différent méthode. Par exemple, dans l'exemple suivant, le premier nom de méthode ne prend pas de paramètre, contrairement au second:

dit bonjour dit bonjour:

Là encore, les conventions de dénomination contribuent largement à éliminer la confusion, mais vous devez tout de même savoir quand il est nécessaire d’ajouter un signe deux-points à la fin du nom de la méthode. Ceci est un problème courant si vous êtes nouveau sur les sélecteurs, et il peut être difficile à déboguer, car les deux points de fin créent toujours un nom de méthode parfaitement valide..

Sélecteurs Performants

Bien sûr, enregistrer un sélecteur dans un SEL variable est relativement inutile sans la possibilité de l’exécuter ultérieurement. Depuis un sélecteur est simplement une méthode prénom (pas une implémentation), il doit toujours être associé à un objet avant de pouvoir l'appeler. le NSObject classe définit un performSelector: méthode à cet effet.

[joe performSelector: @selector (sayHello)];

C'est l'équivalent d'appeler dis bonjour directement sur Joe:

[Joe dit Bonjour];

Pour les méthodes avec un ou deux paramètres, vous pouvez utiliser le code associé. performSelector: withObject: et performSelector: withObject: withObject: méthodes. L'implémentation de méthode suivante:

- (void) sayHelloToPerson: (Person *) aPerson NSLog (@ "Hello,% @", [aPerson name]); 

pourrait être appelé dynamiquement en passant le une personne argument à la performSelector: withObject: méthode, comme démontré ici:

[joe performSelector: @selector (sayHelloToPerson :) withObject: bill];

Cela équivaut à transmettre le paramètre directement à la méthode:

[joe sayHelloToPerson: bill];

De même, le performSelector: withObject: withObject: méthode vous permet de transmettre deux paramètres à la méthode cible. Le seul inconvénient avec ceci est que tous les paramètres et la valeur de retour de la méthode doivent être des objets - ils ne fonctionnent pas avec les types de données C primitifs comme int, flotte, etc. Si vous avez besoin de cette fonctionnalité, vous pouvez cocher le type primitif dans l'une des nombreuses classes d'encapsulation d'Objective-C (par exemple,., NSNumber) ou utilisez l'objet NSInvocation pour encapsuler un appel de méthode complet.

Vérification de l'existence de sélecteurs

Il n'est pas possible d'effectuer un sélecteur sur un objet qui n'a pas défini la méthode associée. Mais contrairement aux appels de méthodes statiques, il est impossible de déterminer au moment de la compilation si performSelector: va soulever une erreur. Au lieu de cela, vous devez vérifier si un objet peut répondre à un sélecteur au moment de l’exécution en utilisant le nom bien nommé. répond au sélecteur: méthode. Il retourne simplement OUI ou NON selon que l'objet peut ou non effectuer le sélecteur:

SEL méthodeToCall = @selector (sayHello); if ([joe répondsToSelector: methodToCall]) [joe performSelector: methodToCall];  else NSLog (@ "Joe ne sait pas comment exécuter% @.", NSStringFromSelector (methodToCall)); 

Si vos sélecteurs sont générés dynamiquement (par exemple, si methodToCall est sélectionné dans une liste d'options) ou vous n'avez pas le contrôle sur l'objet cible (par exemple., Joe peut être l’un de plusieurs types d’objets), il est important d’exécuter cette vérification avant d’essayer d’appeler performSelector:.

Utiliser des sélecteurs

L'idée des sélecteurs est de pouvoir transmettre des méthodes comme vous transmettez des objets. Cela peut être utilisé, par exemple, pour définir dynamiquement une "action" pour un La personne objet à exécuter plus tard dans le programme. Par exemple, considérons l'interface suivante:

Échantillon de code inclus: sélecteurs

@interface Person: NSObject @property (copy) NSString * name; @property (faible) Person * friend; @property SEL action; - (vide) dites bonjour; - (vide) dites au revoir; - (vide) coerceFriend; @fin

Avec l'implémentation correspondante:

#import "Person.h" @implementation Person @synthesize name = _name; @synthesize friend = _friend; @synthèse action = _action; - (void) sayHello NSLog (@ "Bonjour, dit% @.", _name);  - (void) sayGoodbye NSLog (@ "Au revoir, dit% @.", _name);  - (void) coerceFriend NSLog (@ "% @ est sur le point de faire% @ faire quelque chose.", _name, [_name nom]); [_friend performSelector: _action];  @fin

Comme vous pouvez le voir, appelez le coerceFriend méthode forcera un différent objet pour effectuer une action arbitraire. Cela vous permet de configurer une amitié et un comportement au tout début de votre programme et d'attendre qu'un événement particulier se produise avant de déclencher l'action:

#importation  #import "Person.h" NSString * askUserForAction () // Dans le monde réel, il s'agirait de capturer quelques // entrées utilisateur pour déterminer la méthode à appeler. NSString * theMethod = @ "sayGoodbye"; renvoyer la méthode;  int main (int argc, const char * argv []) @autoreleasepool // Créez une personne et déterminez une action à effectuer. Person * joe = [[Person alloc] init]; joe.name = @ "Joe"; Person * bill = [[Person alloc] init]; bill.name = @ "Bill"; joe.friend = bill; joe.action = NSSelectorFromString (askUserForAction ()); // Attendre un événement… // Effectuer l'action. [Joe coerceFriend];  retourne 0; 

C’est presque la façon dont les composants de l’interface utilisateur dans iOS sont mis en œuvre. Par exemple, si vous aviez un bouton, vous le configureriez avec un objet cible (par exemple,., ami) et une action (par exemple., action). Ensuite, lorsque l'utilisateur appuie sur le bouton, il peut utiliser performSelector: exécuter la méthode souhaitée sur l'objet approprié. Permettant à la fois l'objet et la méthode de variation indépendante offre une grande flexibilité - le bouton peut littéralement effectuer n'importe quelle action avec n'importe quel objet sans modifier la classe du bouton. Ceci constitue également la base du modèle de conception Cible-Action, qui est largement utilisé dans le iOS succinctement livre de compagnie.


Résumé

Dans ce chapitre, nous avons abordé les méthodes d'instance et de classe, ainsi que certaines des méthodes intégrées les plus importantes. Nous avons travaillé en étroite collaboration avec les sélecteurs, qui permettent de désigner les noms de méthodes sous forme de code source ou de chaînes. Nous avons également brièvement présenté le modèle de conception Target-Action, qui fait partie intégrante de la programmation iOS et OS X..

Le chapitre suivant examine une autre façon de créer des méthodes privées et protégées dans Objective-C..

Cette leçon représente un chapitre de Objective-C Succinctly, un eBook gratuit de l’équipe de Syncfusion..