Categorieën zijn een Objectief-C-taalfunctie waarmee u nieuwe methoden kunt toevoegen aan een bestaande klasse, net zoals C # -extensies. Verwar C # -extensies echter niet met Objective-C-extensies. De extensies van Objective-C zijn een speciaal geval van categorieën waarmee u methoden definieert die moeten worden gedeclareerd in de belangrijkste implementatieblok.
Dit zijn krachtige functies met veel toepassingsmogelijkheden. Ten eerste maken categorieën het mogelijk om de interface en implementatie van een klasse op te delen in verschillende bestanden, wat de broodnodige modulariteit voor grotere projecten oplevert. Ten tweede kunnen categorieën u bugs in een bestaande klasse (bijv., NSString
) zonder de noodzaak om deze te subclass. Ten derde bieden ze een effectief alternatief voor de beschermde en privé-methoden die worden gevonden in C # en andere Simula-achtige talen.
EEN categorie is een groep verwante methoden voor een klasse en alle methoden die in een categorie zijn gedefinieerd, zijn beschikbaar via de klas alsof ze zijn gedefinieerd in het hoofdinterfacebestand. Neem als voorbeeld de Persoon
klas waarmee we in dit boek hebben gewerkt. Als dit een groot project was, Persoon
kan tientallen methoden hebben, variërend van elementair gedrag tot interacties met andere mensen tot identiteitscontrole. De API kan oproepen dat al deze methoden beschikbaar zijn via een enkele klasse, maar het is veel eenvoudiger voor ontwikkelaars om te handhaven als elke groep in een afzonderlijk bestand is opgeslagen. Bovendien elimineren categorieën de noodzaak om de hele klasse opnieuw te compileren telkens wanneer u een enkele methode wijzigt, wat een tijdsbesparing kan zijn voor zeer grote projecten.
Laten we eens kijken hoe categorieën kunnen worden gebruikt om dit te bereiken. We beginnen met een normale klasse-interface en een bijbehorende implementatie:
// Person.h @interface Persoon: NSObject @interface Persoon: NSObject @property (alleen-lezen) NSMutableArray * vrienden; @property (kopie) NSString * naam; - (ongeldig) zeg Hallo; - (void) sayGoodbye; @end // Person.m #import "Person.h" @implementation Persoon @synthesize name = _name; @synthesize friends = _friends; - (id) init self = [super init]; if (self) _friends = [[NSMutableArray alloc] init]; terugkeer zelf; - (void) sayHello NSLog (@ "Hello, says% @.", _name); - (void) sayGoodbye NSLog (@ "Goodbye, says% @.", _name); @end
Niets nieuws hier - alleen a Persoon
klasse met twee eigenschappen (de vrienden
eigendom wordt gebruikt door onze categorie) en twee methoden. Vervolgens gebruiken we een categorie om enkele methoden op te slaan voor interactie met andere Persoon
instances. Maak een nieuw bestand, maar in plaats van een klasse, gebruik de Objectief-C categorie sjabloon. Gebruik Relaties voor de categorienaam en Persoon voor de Categorie op veld:
Zoals verwacht, zal dit twee bestanden creëren: een header om de interface vast te houden en een implementatie. Deze zullen er echter allemaal iets anders uitzien dan waar we mee hebben gewerkt. Laten we eerst eens kijken naar de interface:
// Person + Relations.h #import#import "Person.h" @interface Persoon (relaties) - (ongeldig) addFriend: (persoon *) aFriend; - (void) removeFriend: (Person *) aFriend; - (ongeldig) sayHelloToFriends; @einde
In plaats van het normale @interface
verklaring, we nemen de categorienaam tussen haakjes na de klassennaam die we uitbreiden. Een categorienaam kan alles zijn, zolang het niet in strijd is met andere categorieën voor dezelfde klasse. Een categorie's het dossier naam moet de klassenaam zijn gevolgd door een plusteken gevolgd door de naam van de categorie (bijv., Persoon + Relations.h
).
Dit definieert dus de interface van onze categorie. Alle methoden die we hier toevoegen, worden toegevoegd aan het origineel Persoon
klasse tijdens runtime. Het zal lijken alsof het vriend toevoegen:
, Verwijder vriend:
, en sayHelloToFriends
methoden zijn allemaal gedefinieerd in Person.h
, maar we kunnen onze functionaliteit ingekapseld en onderhoudbaar houden. Merk ook op dat u de header voor de originele klasse moet importeren, Person.h
. De categorie-implementatie volgt een soortgelijk patroon:
// Person + Relations.m #import "Person + Relations.h" @implementation Person (Relations) - (void) addFriend: (Person *) aFriend [[self friends] addObject: aFriend]; - (void) removeFriend: (Person *) aFriend [[self friends] removeObject: aFriend]; - (ongeldig) sayHelloToFriends for (persoon * vriend in [vrienden]) NSLog (@ "Hallo daar,% @!", [naam vriend]); @end
Dit implementeert alle methoden in Persoon + Relations.h
. Net als de interface van de categorie, staat de naam van de categorie tussen haakjes achter de naam van de klas. De categorienaam in de implementatie moet overeenkomen met die in de interface.
Merk ook op dat er geen manier is om aanvullende eigenschappen of instantievariabelen in een categorie te definiëren. Categorieën moeten verwijzen naar gegevens die zijn opgeslagen in de hoofdklasse (vrienden
op dit moment).
Het is ook mogelijk om de implementatie in te omzeilen Person.m
door simpelweg de methode te herdefiniëren in Persoon + Relations.m
. Dit kan worden gebruikt om een bestaande klasse te patchen; Het wordt echter niet aanbevolen als u een alternatieve oplossing voor het probleem hebt, omdat er geen manier is om de implementatie die door de categorie wordt gedefinieerd, te overschrijven. Dat wil zeggen, in tegenstelling tot de klassehiërarchie zijn categorieën een platte organisatiestructuur: als u dezelfde methode in twee afzonderlijke categorieën implementeert, is het voor de runtime onmogelijk om uit te vinden welke u moet gebruiken.
De enige verandering die u moet aanbrengen om een categorie te gebruiken, is om het header-bestand van de categorie te importeren. Zoals u in het volgende voorbeeld kunt zien, is de Persoon
klasse heeft toegang tot de methoden die zijn gedefinieerd in Person.h
samen met degene die in de categorie zijn gedefinieerd Persoon + 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"; Persoon * factuur = [[Person alloc] init]; bill.name = @ "Factuur"; Persoon * mary = [[Person alloc] init]; mary.name = @ "Mary"; [Joe SayHello]; [joe addFriend: bill]; [joe addFriend: mary]; [joe sayHelloToFriends]; retourneer 0;
En dat is alles wat er is om categorieën te maken in Objective-C.
Om opnieuw te zeggen, allemaal Objectief-C-methoden zijn openbaar - er is geen taalconstructie om ze als privé of beschermd te markeren. In plaats van het gebruik van "echte" beschermde methoden, kunnen Objective-C-programma's categorieën combineren met het interface / implementatieparadigma om hetzelfde resultaat te bereiken.
Het idee is simpel: declareer "beschermde" methoden als een categorie in een apart header-bestand. Dit geeft subklassen de mogelijkheid om zich aan te melden voor de beschermde methoden, terwijl niet-gerelateerde klassen het "openbare" headerbestand gebruiken zoals gewoonlijk. Neem bijvoorbeeld een standaard Schip
interface:
// Ship.h #import@interface Schip: NSObject - (ongeldig) schieten; @einde
Zoals we vaak hebben gezien, definieert dit een openbare methode genaamd schieten
. Om een te declareren beschermde methode, moeten we een Schip
categorie in een speciaal header-bestand:
// Ship_Protected.h #import@interface Ship (Protected) - (void) prepareToShoot; @einde
Alle klassen die toegang nodig hebben tot de beschermde methoden (namelijk de superklasse en eventuele subklassen) kunnen eenvoudig importeren Ship_Protected.h
. Bijvoorbeeld de Schip
implementatie moet een standaardgedrag voor de beschermde methode definiëren:
// Ship.m #import "Ship.h" #import "Ship_Protected.h" @implementation Ship BOOL _gunIsReady; - (ongeldig) shoot if (! _gunIsReady) [self prepareToShoot]; _gunIsReady = YES; NSLog (@ "Firing!"); - (void) prepareToShoot // Voer enkele privéfunctionaliteit uit. NSLog (@ "Het hoofdwapen voorbereiden ..."); @end
Merk op dat als we niet hadden geïmporteerd Ship_Protected.h
, deze prepareToShoot
implementatie zou een private methode zijn, zoals besproken in de Methoden hoofdstuk. Zonder een beschermde categorie zouden subklassen geen toegang hebben tot deze methode. Laten we de Schip
om te zien hoe dit werkt. We zullen het noemen ResearchShip
:
// ResearchShip.h #import "Ship.h" @interface ResearchShip: Ship - (void) extendTelescope; @einde
Dit is een normale subklasse-interface - dat zou het moeten doen niet importeer de beschermde header, omdat dit de beveiligde methoden beschikbaar maakt voor iedereen die importeert ResearchShip.h
, dat is precies wat we proberen te vermijden. Ten slotte importeert de implementatie voor de subklasse de beschermde methoden en (optioneel) deze:
// ResearchShip.m #import "ResearchShip.h" #import "Ship_Protected.h" @implementation ResearchShip - (void) extendTelescope NSLog (@ "De telescoop uitbreiden"); // Override protected method - (void) prepareToShoot NSLog (@ "Oh shoot! We moeten enkele wapens vinden!"); @end
Om de beschermde status van de methoden in te voeren Ship_Protected.h
, andere klassen mogen dit niet importeren. Ze importeren gewoon de normale "openbare" interfaces van de superklasse en subklasse:
// main.m #import#import "Ship.h" #import "ResearchShip.h" int main (int argc, const char * argv []) @autoreleasepool Ship * genericShip = [[Ship alloc] init]; [genericShip shoot]; Ship * discoveryOne = [[ResearchShip-allocatie] init]; [discoveryOne shoot]; retourneer 0;
Omdat geen van beide main.m
, Ship.h
, noch ResearchShip.h
importeer de beschermde methoden, deze code heeft geen toegang tot hen. Probeer een toe te voegen [discoveryOne prepareToShoot]
methode-het zal een compileerfout geven, omdat de prepareToShoot
verklaring is nergens te vinden.
Samengevat kunnen beschermde methoden worden geëmuleerd door ze in een speciaal headerbestand te plaatsen en dat headerbestand te importeren in de implementatiebestanden die toegang tot de beschermde methoden vereisen. Geen andere bestanden mogen de beschermde header importeren.
Hoewel de hier gepresenteerde workflow een volledig geldige organisatorische tool is, moet u er rekening mee houden dat Objective-C nooit bedoeld was om beschermde methoden te ondersteunen. Zie dit als een alternatieve manier om een Objective-C-methode te structureren in plaats van een directe vervanging voor C # / Simula-stijl beschermde methoden. Het is vaak beter om naar een andere manier te zoeken om uw klassen te structureren dan om uw Objective-C-code te dwingen om te handelen als een C # -programma.
Een van de grootste problemen met categorieën is dat u methoden die in categorieën voor dezelfde klasse zijn gedefinieerd, niet op betrouwbare wijze kunt overschrijven. Bijvoorbeeld als u een hebt gedefinieerd vriend toevoegen:
klasse in Persoon (Betrekkingen)
en later besloten om de vriend toevoegen:
implementatie via een Persoon (Veiligheid)
categorie, is er geen manier voor de runtime om te weten welke methode moet worden gebruikt, omdat categorieën per definitie een platte organisatiestructuur zijn. Voor dit soort gevallen moet u terugkeren naar het traditionele subklassen-paradigma.
Het is ook belangrijk op te merken dat een categorie geen instantievariabelen kan toevoegen. Dit betekent dat u geen nieuwe eigenschappen in een categorie kunt declareren, omdat deze alleen kunnen worden gesynthetiseerd in de hoofdimplementatie. Hoewel een categorie technisch gezien wel toegang heeft tot de instantievariabelen van de klassen, is het beter om ze via hun openbare interface te benaderen om de categorie te beschermen tegen mogelijke wijzigingen in het hoofdimplementatiebestand..
uitbreidingen (ook wel genoemd klasse-extensies) zijn een speciaal type categorie waarvoor hun methoden moeten worden gedefinieerd in de hoofd implementatieblok voor de bijbehorende klasse, in tegenstelling tot een implementatie gedefinieerd in een categorie. Dit kan worden gebruikt om eigenschappen van openbaar verklaard onroerend goed te overschrijven. Het is bijvoorbeeld soms handig om een alleen-lezen-eigenschap te wijzigen in een lees-schrijf-eigenschap binnen de implementatie van een klasse. Overweeg de normale interface voor a Schip
klasse:
Inclusief codevoorbeeld: Extensies
// Ship.h #import#import "Person.h" @interface Schip: NSObject @property (sterk, alleen-lezen) Persoon * aanvoerder; - (id) initWithCaptain: (persoon *) kapitein; @einde
Het is mogelijk om de @eigendom
definitie binnen een klasse-extensie. Dit geeft u de mogelijkheid om het eigendom opnieuw te declareren als lezen schrijven
in het implementatiebestand. Syntactisch lijkt een extensie op een lege categorieverklaring:
// Ship.m #import "Ship.h" // De klasse-extensie. @interface Ship () @property (strong, readwrite) Persoon * aanvoerder; @end // De standaardimplementatie. @implementation Ship @synthesize captain = _captain; - (id) initWithCaptain: (Persoon *) kapitein self = [super init]; if (self) // Dit zal werken vanwege de extensie. [self setCaptain: kapitein]; terugkeer zelf; @end
Merk op ()
toegevoegd aan de klassenaam na de @interface
richtlijn. Dit is wat het markeert als een extensie in plaats van een normale interface of een categorie. Alle eigenschappen of methoden die in de extensie worden weergegeven moet worden gedeclareerd in het hoofdimplementatieblok voor de klas. In dit geval voegen we geen nieuwe velden toe. We overschrijven een bestaande. Maar in tegenstelling tot categorieën, extensies kan extra instantievariabelen toevoegen aan een klasse. Daarom kunnen we eigenschappen in een klassenextensie declareren, maar geen categorie.
Omdat we het opnieuw hebben uitgeroepen gezagvoerder
eigendom met een lezen schrijven
attribuut, de initWithCaptain:
methode kan de setCaptain:
accessor op zichzelf. Als u de extensie zou verwijderen, zou de eigenschap terugkeren naar de alleen-lezen status en zou de compiler klagen. Clients die de. Gebruiken Schip
klasse is niet van plan het implementatiebestand te importeren, dus de gezagvoerder
eigenschap blijft alleen-lezen.
#importeren#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 (@ "% @", [discoveryOne captain] .name); Persoon * dave = [[Person alloc] init]; dave.name = @ "Dave"; // Dit zal NIET werken omdat de eigenschap nog steeds alleen-lezen is. [discoveryOne setCaptain: dave]; retourneer 0;
Een ander vaak voorkomend geval voor uitbreidingen is het declareren van privé-methoden. In het vorige hoofdstuk hebben we gezien hoe privémethoden kunnen worden gedeclareerd door ze eenvoudig overal in het implementatiebestand toe te voegen. Maar vóór Xcode 4.3 was dit niet het geval. De canonieke manier om een privémethode te maken, was om deze door te sturen met een klasse-extensie. Laten we dit eens bekijken door de Schip
kop van het vorige voorbeeld:
// Ship.h #import@interface Schip: NSObject - (ongeldig) schieten; @einde
Vervolgens gaan we het voorbeeld opnieuw maken dat we hebben gebruikt toen we de privémethoden in de Methoden hoofdstuk. In plaats van simpelweg het privé toevoegen prepareToShoot
methode voor de implementatie, moeten we forward-declare in een klasse-extensie.
// Ship.m #import "Ship.h" // De klasse-extensie. @interface Ship () - (void) prepareToShoot; @end // De rest van de implementatie. @implementation Ship BOOL _gunIsReady; - (ongeldig) shoot if (! _gunIsReady) [self prepareToShoot]; _gunIsReady = YES; NSLog (@ "Firing!"); - (void) prepareToShoot // Voer enkele privéfunctionaliteit uit. NSLog (@ "Het hoofdwapen voorbereiden ..."); @end
De compiler zorgt ervoor dat de uitbreidingsmethoden worden geïmplementeerd in het hoofdimplementatieblok, en daarom werkt het als een forward-declaratie. Maar omdat de extensie is ingekapseld in het implementatiebestand, moeten andere objecten dit nooit weten, waardoor we op een andere manier privémethoden kunnen emuleren. Hoewel nieuwere compilers u deze problemen besparen, is het nog steeds belangrijk om te begrijpen hoe klasse-uitbreidingen werken, omdat dit tot voor kort een veelgebruikte manier was om gebruik te maken van privé-methoden in Objective-C-programma's..
Dit hoofdstuk behandelde twee van de meer unieke concepten in de programmeertaal Objective-C: categorieën en uitbreidingen. Categorieën zijn een manier om de API van bestaande klassen uit te breiden en extensies zijn een manier om toe te voegen verplicht methoden voor de API buiten het hoofdinterfacebestand. Beide waren aanvankelijk ontworpen om de last van het onderhouden van grote codebases te verlichten.
Het volgende hoofdstuk gaat verder op onze reis door de organisatiestructuren van Objective-C. We leren hoe we een protocol kunnen definiëren, een interface die door verschillende klassen kan worden geïmplementeerd.
Deze les vertegenwoordigt een hoofdstuk uit Objective-C bondig, een gratis eBoek van het team van Syncfusion.