Les catégories sont une fonctionnalité du langage Objective-C qui vous permet d'ajouter de nouvelles méthodes à une classe existante, un peu comme les extensions C #. Cependant, ne confondez pas les extensions C # avec les extensions Objective-C. Les extensions d'Objective-C sont un cas particulier de catégories qui vous permettent de définir des méthodes à déclarer dans la bloc de mise en œuvre principal.
Ce sont des fonctionnalités puissantes qui ont de nombreuses utilisations potentielles. Premièrement, les catégories permettent de scinder l’interface et la mise en oeuvre d’une classe en plusieurs fichiers, ce qui offre la modularité indispensable aux projets de grande envergure. Deuxièmement, les catégories vous permettent de corriger les bugs dans une classe existante (par exemple,., NSString
) sans qu'il soit nécessaire de le sous-classer. Troisièmement, ils offrent une alternative efficace aux méthodes protégées et privées trouvées en C # et dans d'autres langages similaires à Simula..
UNE Catégorie est un groupe de méthodes associées pour une classe. Toutes les méthodes définies dans une catégorie sont disponibles via la classe comme si elles avaient été définies dans le fichier d'interface principal. Par exemple, prenons le La personne
classe avec laquelle nous avons travaillé tout au long de ce livre. Si c'était un grand projet, La personne
peut avoir des dizaines de méthodes allant des comportements de base aux interactions avec d’autres personnes, en passant par la vérification d’identité. L'API peut demander que toutes ces méthodes soient disponibles via une seule classe, mais il est beaucoup plus facile pour les développeurs de la maintenir si chaque groupe est stocké dans un fichier séparé. De plus, les catégories éliminent le besoin de recompiler la classe entière chaque fois que vous modifiez une seule méthode, ce qui peut vous faire gagner du temps pour les très grands projets..
Voyons comment les catégories peuvent être utilisées pour y parvenir. Nous commençons avec une interface de classe normale et une implémentation correspondante:
// Person.h @interface Person: NSObject @interface Person: NSObject @property (en lecture seule) NSMutableArray * friends; @property (copy) NSString * name; - (vide) dites bonjour; - (vide) dites au revoir; @end // Person.m #import "Person.h" @implementation Person @synthesize name = _name; @synthesize friends = _friends; - (id) init self = [super init]; if (self) _friends = [[NSMutableArray alloc] init]; retourner soi-même; - (void) sayHello NSLog (@ "Bonjour, dit% @.", _name); - (void) sayGoodbye NSLog (@ "Au revoir, dit% @.", _name); @fin
Rien de nouveau ici, juste un La personne
classe avec deux propriétés (le copains
propriété sera utilisé par notre catégorie) et deux méthodes. Ensuite, nous allons utiliser une catégorie pour stocker des méthodes d’interaction avec d’autres La personne
les instances. Créez un nouveau fichier, mais au lieu d’une classe, utilisez le Catégorie Objective-C modèle. Utilisation Rapports pour le nom de la catégorie et La personne pour le Catégorie sur champ:
Comme prévu, cela créera deux fichiers: un en-tête pour l'interface et une implémentation. Cependant, ces deux aspects seront légèrement différents de ceux sur lesquels nous travaillons. Tout d'abord, jetons un coup d'oeil à l'interface:
// Personne + Relations.h #import#import "Person.h" @interface Person (Relations) - (void) addFriend: (Person *) aFriend; - (vide) removeFriend: (Person *) aFriend; - (void) sayHelloToFriends; @fin
Au lieu de la normale @interface
déclaration, nous incluons le nom de la catégorie entre parenthèses après le nom de la classe que nous étendons. Un nom de catégorie peut être n'importe quoi, à condition qu'il ne soit pas en conflit avec d'autres catégories pour la même classe. Une catégorie fichier nom doit être le nom de la classe suivi du signe plus, suivi du nom de la catégorie (par exemple,., Personne + Relations.h
).
Donc, cela définit l'interface de notre catégorie. Toutes les méthodes que nous ajoutons ici seront ajoutées à l'original La personne
classe au moment de l'exécution. Il semblera que le Ajouter un ami:
, supprimer un ami:
, et sayHelloToFriends
les méthodes sont toutes définies dans Personne.h
, mais nous pouvons garder notre fonctionnalité encapsulée et maintenable. Notez également que vous devez importer l'en-tête de la classe d'origine., Personne.h
. L'implémentation de la catégorie suit un schéma similaire:
// Person + Relations.m #import "Person + Relations.h" @implementation Person (Relations) - (void) addFriend: (Person *) aFriend [[auto-amis] addObject: aFriend]; - (void) removeFriend: (Person *) aFriend [[auto-amis] removeObject: aFriend]; - (void) sayHelloToFriends pour (Personne * ami dans [amis personnels]) NSLog (@ "Bonjour,% @!", [nom de l'ami]); @fin
Ceci implémente toutes les méthodes de Personne + Relations.h
. Tout comme l'interface de la catégorie, le nom de la catégorie apparaît entre parenthèses après le nom de la classe. Le nom de la catégorie dans l'implémentation doit correspondre à celui de l'interface.
Notez également qu'il est impossible de définir des propriétés ou des variables d'instance supplémentaires dans une catégorie. Les catégories doivent renvoyer aux données stockées dans la classe principale (copains
dans ce cas).
Il est également possible de remplacer la mise en oeuvre contenue dans Personne.m
en redéfinissant simplement la méthode Personne + relations.m
. Ceci peut être utilisé pour patcher une classe existante. Cependant, il n'est pas recommandé si vous avez une solution alternative au problème, car il n'y aurait aucun moyen de remplacer l'implémentation définie par la catégorie. Autrement dit, contrairement à la hiérarchie des classes, les catégories constituent une structure organisationnelle plate. Si vous implémentez la même méthode dans deux catégories distinctes, il est impossible pour le moteur d'exécution de choisir celle à utiliser..
La seule modification que vous devez apporter pour utiliser une catégorie consiste à importer le fichier d'en-tête de la catégorie. Comme vous pouvez le voir dans l'exemple suivant, le La personne
classe a accès aux méthodes définies dans Personne.h
ainsi que ceux définis dans la catégorie Personne + Relations.h
:
// main.m #import#import "Person.h" #import "Person + Relations.h" int main (int argc, const char * argv []) @autoreleasepool Person * joe = [[Person alloc] init]; joe.name = @ "Joe"; Person * bill = [[Person alloc] init]; bill.name = @ "Bill"; Person * mary = [[Person alloc] init]; mary.name = @ "Mary"; [Joe dit Bonjour]; [Joe addFriend: facture]; [Joe addFriend: Marie]; [joe sayHelloToFriends]; retourne 0;
Et c'est tout ce qu'il y a à créer des catégories dans Objective-C.
Recommencer, tout Les méthodes Objective-C sont publiques: il n'y a pas de construction de langage pour les marquer comme privées ou protégées. Au lieu d'utiliser de "vraies" méthodes protégées, les programmes Objective-C peuvent combiner des catégories avec le paradigme interface / implémentation pour obtenir le même résultat..
L'idée est simple: déclarez les méthodes "protégées" en tant que catégorie dans un fichier d'en-tête séparé. Cela donne aux sous-classes la possibilité d’adhérer aux méthodes protégées tandis que les classes non liées utilisent le fichier d’en-tête "public" comme d’habitude. Par exemple, prenons une norme Navire
interface:
// Ship.h #import@interface Ship: NSObject - (vide) shoot; @fin
Comme nous l'avons vu à plusieurs reprises, cela définit une méthode publique appelée tirer
. Déclarer un protégé méthode, nous devons créer un Navire
catégorie dans un fichier d'en-tête dédié:
// Ship_Protected.h #import@interface Ship (Protected) - (void) prepareToShoot; @fin
Toute classe ayant besoin d’accéder aux méthodes protégées (à savoir la superclasse et toutes les sous-classes) peut simplement importer Ship_Protected.h
. Par exemple, le Navire
l'implémentation devrait définir un comportement par défaut pour la méthode protégée:
// Ship.m #import "Ship.h" #import "Ship_Protected.h" @implementation Ship BOOL _gunIsReady; - (void) 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
Notez que si nous n'avions pas importé Ship_Protected.h
, ce préparerSocher
la mise en œuvre serait une méthode privée, comme indiqué dans le Chapitre Méthodes. Sans une catégorie protégée, les sous-classes n'auraient aucun moyen d'accéder à cette méthode. Subclassons le Navire
pour voir comment cela fonctionne. Nous l'appellerons Recherche
:
// ResearchShip.h #import "Ship.h" @interface ResearchShip: Ship - (void) extendTelescope; @fin
Ceci est une interface de sous-classe normale, il devrait ne pas importer l'en-tête protégé, car cela rendrait les méthodes protégées accessibles à toute personne qui importe ResearchShip.h
, c'est précisément ce que nous essayons d'éviter. Enfin, l'implémentation de la sous-classe importe les méthodes protégées et les remplace (éventuellement):
// ResearchShip.m #import "ResearchShip.h" #import "Ship_Protected.h" @implementation ResearchShip - (void) extendTelescope NSLog (@ "Extension du télescope"); // Remplace la méthode protégée - (void) prepareToShoot NSLog (@ "Oh shoot! Nous devons trouver des armes!"); @fin
Pour appliquer le statut protégé des méthodes dans Ship_Protected.h
, les autres classes ne sont pas autorisées à l'importer. Ils vont simplement importer les interfaces "publiques" normales de la superclasse et de la sous-classe:
// main.m #import#import "Ship.h" #import "ResearchShip.h" int main (int argc, const char * argv []) @autoreleasepool Ship * genericShip = [[Ship alloc] init]; [shoot générique]; Ship * discoveryOne = [[ResearchShip alloc] init]; [découverteOne shoot]; retourne 0;
Depuis ni main.m
, Ship.h
, ni ResearchShip.h
importer les méthodes protégées, ce code n'y aura pas accès. Essayez d'ajouter un [discoveryOne prepareToShoot]
method-it générera une erreur de compilation, car le préparerSocher
la déclaration est introuvable.
Pour résumer, les méthodes protégées peuvent être émulées en les plaçant dans un fichier d'en-tête dédié et en important ce fichier d'en-tête dans les fichiers d'implémentation nécessitant un accès aux méthodes protégées. Aucun autre fichier ne doit importer l'en-tête protégé.
Bien que le flux de travail présenté ici soit un outil d'organisation parfaitement valide, gardez à l'esprit qu'Objective-C n'a jamais été conçu pour prendre en charge des méthodes protégées. Considérez cela comme un moyen alternatif de structurer une méthode Objective-C plutôt que de remplacer directement les méthodes protégées de type C # / Simula. Il est souvent préférable de rechercher un autre moyen de structurer vos classes plutôt que de forcer votre code Objective-C à se comporter comme un programme C #..
L'un des problèmes les plus importants avec les catégories est qu'il est impossible de remplacer de manière fiable les méthodes définies dans les catégories de la même classe. Par exemple, si vous avez défini un Ajouter un ami:
cours en Personne (relations)
et a ensuite décidé de changer le Ajouter un ami:
mise en œuvre via un Personne (sécurité)
catégorie, il n’existe aucun moyen pour le moteur d’exécution de savoir quelle méthode utiliser, car les catégories sont, par définition, une structure organisationnelle plate. Pour ce genre de cas, vous devez revenir au paradigme traditionnel des sous-classes..
De plus, il est important de noter qu'une catégorie ne peut pas ajouter de variables d'instance. Cela signifie que vous ne pouvez pas déclarer de nouvelles propriétés dans une catégorie, car elles ne peuvent être synthétisées que dans l'implémentation principale. De plus, bien qu'une catégorie ait techniquement accès aux variables d'instance de ses classes, il est préférable de les utiliser via leur interface publique pour la protéger des modifications potentielles dans le fichier principal d'implémentation..
Les extensions (aussi appelé extensions de classe) sont un type particulier de catégorie qui exige que leurs méthodes soient définies dans principale bloc d'implémentation pour la classe associée, par opposition à une implémentation définie dans une catégorie. Ceci peut être utilisé pour remplacer les attributs de propriété déclarés publiquement. Par exemple, il est parfois pratique de remplacer une propriété en lecture seule par une propriété en lecture / écriture dans l'implémentation d'une classe. Considérez l'interface normale pour un Navire
classe:
Échantillon de code inclus: Extensions
// Ship.h #import#import "Person.h" @interface Ship: NSObject @property (strong, en lecture seule) Person * captain; - (id) initWithCaptain: (personne *) capitaine; @fin
Il est possible de remplacer le @propriété
définition à l'intérieur d'une extension de classe. Cela vous donne la possibilité de re-déclarer la propriété en tant que lire écrire
dans le fichier d'implémentation. Syntaxiquement, une extension ressemble à une déclaration de catégorie vide:
// Ship.m #import "Ship.h" // L'extension de classe. @interface Ship () @property (strong, readwrite) Personne * capitaine; @end // L'implémentation standard. @implementation Ship @synthesize capitaine = _captain; - (id) initWithCaptain: (Person *) capitaine self = [super init]; if (self) // Cela fonctionnera à cause de l'extension. [self setCaptain: capitaine]; retourner soi-même; @fin
Noter la ()
ajouté au nom de la classe après le @interface
directif. C’est ce qui le marque comme une extension plutôt que comme une interface ou une catégorie normale. Toutes les propriétés ou méthodes qui apparaissent dans l'extension doit être déclaré dans le bloc d'implémentation principal de la classe. Dans ce cas, nous n’ajoutons aucun nouveau champ, nous en substituons un autre. Mais contrairement aux catégories, les extensions pouvez ajouter des variables d'instance supplémentaires à une classe, c'est pourquoi nous sommes en mesure de déclarer des propriétés dans une extension de classe mais pas dans une catégorie.
Parce que nous avons re-déclaré la capitaine
propriété avec un lire écrire
attribut, le initWithCaptain:
méthode peut utiliser le setCaptain:
accesseur sur lui-même. Si vous deviez supprimer l'extension, la propriété reviendrait à son statut en lecture seule et le compilateur se plaindrait. Les clients utilisant le Navire
classe ne sont pas censés importer le fichier d'implémentation, de sorte que le capitaine
la propriété restera en lecture seule.
#importation#import "Person.h" #import "Ship.h" int main (int argc, const char * argv []) @autoreleasepool Person * heywood = [[Person alloc] init]; heywood.name = @ "Heywood"; Ship * discoveryOne = [[Ship alloc] initWithCaptain: heywood]; NSLog (@ "% @", [capitaine de découverteOne] .name); Person * dave = [[Person alloc] init]; dave.name = @ "Dave"; // Cela NE fonctionnera PAS car la propriété est encore en lecture seule. [discoveryOne setCaptain: dave]; retourne 0;
Un autre cas d'utilisation courant des extensions concerne la déclaration de méthodes privées. Dans le chapitre précédent, nous avons vu comment déclarer des méthodes privées en les ajoutant simplement n'importe où dans le fichier d'implémentation. Mais avant Xcode 4.3, ce n'était pas le cas. La manière canonique de créer une méthode privée consistait à la déclarer en utilisant une extension de classe. Jetons un coup d'oeil à cela en modifiant légèrement la Navire
en-tête de l'exemple précédent:
// Ship.h #import@interface Ship: NSObject - (vide) shoot; @fin
Ensuite, nous allons recréer l'exemple que nous avons utilisé quand nous avons discuté des méthodes privées dans le Chapitre Méthodes. Au lieu d'ajouter simplement le privé préparerSocher
méthode à l'implémentation, nous avons besoin de la déclarer en avant dans une extension de classe.
// Ship.m #import "Ship.h" // L'extension de classe. @interface Ship () - (void) prepareToShoot; @end // Le reste de l'implémentation. @implementation Ship BOOL _gunIsReady; - (void) 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
Le compilateur s'assure que les méthodes d'extension sont implémentées dans le bloc d'implémentation principal, raison pour laquelle il fonctionne comme une déclaration en aval. Cependant, étant donné que l'extension est encapsulée dans le fichier d'implémentation, les autres objets ne devraient jamais le savoir, ce qui nous donne un autre moyen d'émuler des méthodes privées. Bien que les compilateurs récents vous épargnent ce problème, il est toujours important de comprendre le fonctionnement des extensions de classe, car c'était un moyen courant de tirer parti des méthodes privées dans les programmes Objective-C jusqu'à tout récemment.
Ce chapitre couvrait deux des concepts les plus uniques du langage de programmation Objective-C: les catégories et les extensions. Les catégories sont un moyen d’étendre l’API de classes existantes et les extensions d’ajouter Champs obligatoires méthodes à l’API en dehors du fichier d’interface principal. Ces deux logiciels ont été initialement conçus pour alléger le fardeau de la maintenance de bases de code volumineuses.
Le chapitre suivant continue notre parcours à travers les structures organisationnelles d'Objective-C. Nous allons apprendre à définir un protocole, une interface pouvant être implémentée par diverses classes..
Cette leçon représente un chapitre de Objective-C Succinctly, un eBook gratuit de l’équipe de Syncfusion..