Mål-C Succinkt Kategorier og utvidelser

Kategorier er en objektiv-C-språkfunksjon som lar deg legge til nye metoder i en eksisterende klasse, i likhet med C # -utvidelser. Men ikke forveksle C # -utvidelser med Objective-C-utvidelser. Mål-Cs utvidelser er et spesielt tilfelle av kategorier som lar deg definere metoder som må deklareres i hovedimplementeringsblokk.

Disse er kraftige funksjoner som har mange potensielle bruksområder. For det første gjør kategorier det mulig å dele opp et klasses grensesnitt og implementering i flere filer, noe som gir tiltrukket modularitet for større prosjekter. For det andre, kategorier lar deg fikse feil i en eksisterende klasse (f.eks., NSString) uten å måtte underklasse det. For det tredje gir de et effektivt alternativ til de beskyttede og private metodene som finnes i C # og andre Simula-lignende språk.


kategorier

EN kategori er en gruppe relaterte metoder for en klasse, og alle metodene som er definert i en kategori, er tilgjengelige gjennom klassen som om de ble definert i hovedgrensesnittfilen. Som et eksempel, ta Person klasse som vi har jobbet med gjennom hele denne boken. Hvis dette var et stort prosjekt, Person kan ha dusinvis av metoder som spenner fra grunnleggende atferd til samspill med andre mennesker til identitetskontroll. API-en kan kreve at alle disse metodene er tilgjengelige gjennom en enkelt klasse, men det er mye lettere for utviklere å opprettholde om hver gruppe er lagret i en egen fil. I tillegg eliminerer kategorier behovet for å kompilere hele klassen hver gang du bytter en enkelt metode, noe som kan være en tidsbesparende for meget store prosjekter.

La oss ta en titt på hvordan kategorier kan brukes til å oppnå dette. Vi starter med et normalt klassegrensesnitt og en tilsvarende implementering:

// Person.h @interface Person: NSObject @interface Person: NSObject @property (readonly) NSMutableArray * venner; @property (copy) NSString * navn; - (void) sayHello; - (void) sayGoodbye; @end // Person.m #import "Person.h" @implementation Person @synthesize navn = _name; @synthesize friends = _friends; - (id) init self = [super init]; hvis (selv) _friends = [[NSMutableArray alloc] init];  returner selv;  - (void) sayHello NSLog (@ "Hei, sier% @.", _name);  - (void) sayGoodbye NSLog (@ "Farvel, sier% @.", _name);  @slutt

Ingenting nytt her-bare en Person klasse med to egenskaper (the venner eiendom vil bli brukt av vår kategori) og to metoder. Deretter bruker vi en kategori for å lagre noen metoder for samhandling med andre Person forekomster. Opprett en ny fil, men i stedet for en klasse, bruk Objektiv-C-kategori mal. Bruk relasjoner for kategorinavnet og Person for Kategori på felt:

Oppretter klassen Person + Relasjoner

Som forventet, vil dette opprette to filer: en header for å holde grensesnittet og en implementering. Men disse vil begge se litt annerledes ut enn det vi har jobbet med. Først, la oss ta en titt på grensesnittet:

// Person + Relasjoner.h #import  #import "Person.h" @interface Person (Forhold) - (void) addFriend: (Person *) aFriend; - (void) removeFriend: (Person *) aFriend; - (ugyldig) sayHelloToFriends; @slutt

I stedet for det normale @interface erklæring, vi inkluderer kategorinavnet i parentes etter klassenavnet vi strekker. Et kategorinavn kan være alt, så lenge det ikke er i konflikt med andre kategorier for samme klasse. En kategori er fil navn skal være klassenavnet etterfulgt av et plustegn, etterfulgt av navnet på kategorien (f.eks., Person + Relations.h ).

Så, dette definerer kategorien vår grensesnitt. Eventuelle metoder vi legger til her vil bli lagt til originalen Person klasse på kjøretid. Det vil se ut som om legge til venn:, removeFriend:, og sayHelloToFriends metodene er alle definert i Person.h, men vi kan holde vår funksjonalitet innkapslet og vedlikeholdsbar. Vær også oppmerksom på at du må importere toppteksten for den opprinnelige klassen, Person.h. Kategori implementeringen følger et lignende mønster:

// Person + Relations.m #import "Person + Relations.h" @implementation Person (Forhold) - (void) addFriend: (Person *) aFriend [[self friends] addObject: aFriend];  - (void) removeFriend: (Person *) aFriend [[self friends] removeObject: aFriend];  - (void) sayHelloToFriends for (Person * venn i [selvvenn]) NSLog (@ "Hei der,% @!", [vennnavn]);   @slutt

Dette implementerer alle metodene i Person + Relations.h. På samme måte som kategoriens grensesnitt, vises kategorinavnet i parentes etter klassenavnet. Kategorienavnet i implementeringen bør samsvare med grensesnittet.

Vær også oppmerksom på at det ikke er mulig å definere flere egenskaper eller forekomstvariabler i en kategori. Kategorier må referere tilbake til data lagret i hovedklassen (venner I dette tilfellet).

Det er også mulig å overstyre implementeringen som finnes i Person.m ved å omdefinere metoden i Person + Relations.m. Dette kan brukes til å ape lapp en eksisterende klasse; Det anbefales imidlertid ikke hvis du har en alternativ løsning på problemet, siden det ikke ville være mulig å overstyre implementeringen definert av kategorien. Det vil si, i motsetning til klasshierarkiet, er kategorier en flatt organisasjonsstruktur - hvis du implementerer samme metode i to separate kategorier, er det umulig for runtime å finne ut hvilken bruker du skal bruke.

Den eneste forandringen du må gjøre for å bruke en kategori er å importere kategoriens headerfil. Som du kan se i følgende eksempel, er Person Klassen har tilgang til metodene som er definert i Person.h sammen med de som er definert i kategorien Person + Relations.h:

// main.m #import  #import "Person.h" #import "Person + Relations.h" int main (int argc, const char * argv []) @autoreleasepool Person * joe = [[Personallokering] init]; joe.name = @ "Joe"; Person * bill = [[Personallokering] init]; bill.name = @ "Bill"; Person * mary = [[Personallokering] init]; mary.name = @ "Mary"; [joe sayHello]; [joe addFriend: bill]; [joe addFriend: mary]; [joe sayHelloToFriends];  returnere 0; 

Og det er alt som er å skape kategorier i Objective-C.

Beskyttede metoder

Å gjenta, alle Mål-C-metoder er offentlige - det er ingen språkkonstruksjon som markerer dem som enten privat eller beskyttet. I stedet for å bruke "ekte" beskyttede metoder kan Objective-C-programmer kombinere kategorier med grensesnittet / implementeringsparadigmet for å oppnå det samme resultatet.

Ideen er enkel: erklære "beskyttede" metoder som en kategori i en egen headerfil. Dette gir underklasser muligheten til å "opt-in" til de beskyttede metodene mens ikke-relaterte klasser bruker den "offentlige" headerfilen som vanlig. For eksempel, ta en standard Skip grensesnitt:

// Ship.h #import  @ Interface Ship: NSObject - (void) shoot; @slutt

Som vi har sett mange ganger, definerer dette en offentlig metode som kalles skyte. Å erklære a beskyttet metode, må vi opprette en Skip kategori i en dedikert headerfil:

// Ship_Protected.h #import  @ Interface Ship (Protected) - (void) prepareToShoot; @slutt

Eventuelle klasser som trenger tilgang til beskyttede metoder (nemlig superklassen og noen underklasser) kan enkelt importere Ship_Protected.h. For eksempel, Skip implementering bør definere en standard oppførsel for den beskyttede metoden:

// Ship.m #import "Ship.h" #import "Ship_Protected.h" @implementation Ship BOOL _gunIsReady;  - (void) shoot if (! _gunIsReady) [self prepareToShoot]; _gunIsReady = JA;  NSLog (@ "Firing!");  - (void) prepareToShoot // Utfør noen privat funksjonalitet. NSLog (@ "Forbereder hovedvåpenet ...");  @slutt

Merk at hvis vi ikke hadde importert Ship_Protected.h, dette prepareToShoot implementering vil være en privat metode, som diskutert i Metoder kapittel. Uten en beskyttet kategori, ville det ikke være noen måte for undergrupper å få tilgang til denne metoden. La oss subklassere Skip for å se hvordan dette virker. Vi ringer det ResearchShip:

// ResearchShip.h #import "Ship.h" @ interface ResearchShip: Ship - (void) extendTelescope; @slutt

Dette er et normalt underklasse-grensesnitt, det burde ikke importer den beskyttede overskriften, da dette ville gjøre de beskyttede metodene tilgjengelige for alle som importerer ResearchShip.h, Det er nettopp det vi prøver å unngå. Endelig importerer implementeringen for underkategorien de beskyttede metodene og (eventuelt) overstyrer dem:

// ResearchShip.m #import "ResearchShip.h" #import "Ship_Protected.h" @implementation ResearchShip - (void) extendTelescope NSLog (@ "Utvide teleskopet");  // Overstyr beskyttet metode - (void) prepareToShoot NSLog (@ "Å skyte! Vi må finne noen våpen!");  @slutt

For å håndheve beskyttet status for metodene i Ship_Protected.h, Andre klasser har ikke lov til å importere det. De vil bare importere de normale "offentlige" grensesnittene til superklassen og underklassen:

// main.m #import  #import "Ship.h" #import "ResearchShip.h" int main (int argc, const char * argv []) @ autoreleasepool Ship * genericShip = [[Ship alloc] init]; [generisk skyte] Ship * discoveryOne = [[ResearchShip alloc] init]; [discoveryOne shoot];  returnere 0; 

Siden heller ikke main.m, Ship.h, eller ResearchShip.h importere beskyttede metoder, vil denne koden ikke ha tilgang til dem. Prøv å legge til en [discoveryOne prepareToShoot] metode-det vil kaste en kompilator feil, siden prepareToShoot erklæringen er ingensteds å finne.

For å oppsummere kan beskyttede metoder emuleres ved å plassere dem i en dedikert headerfil og importere den headerfilen til implementeringsfilene som krever tilgang til de beskyttede metodene. Ingen andre filer skal importere den beskyttede overskriften.

Mens arbeidsflyten som presenteres her, er et helt gyldig organisasjonsverktøy, husk at Objective-C aldri var ment å støtte beskyttede metoder. Tenk på dette som en alternativ måte å strukturere en Objective-C metode, i stedet for en direkte erstatning for C # / Simula-stil beskyttede metoder. Det er ofte bedre å se etter en annen måte å strukturere klassene dine i stedet for å tvinge mål-C-koden til å fungere som et C # -program.

Advarsler

Et av de største problemene med kategorier er at du ikke kan overstyre metoder som er definert i kategorier for samme klasse på en pålitelig måte. For eksempel, hvis du definerte en legge til venn: klasse i Person (Relations) og senere bestemte seg for å endre legge til venn: gjennomføring via a Person (Security) kategori, er det ingen måte for kjøretiden å vite hvilken metode den skal bruke siden kategorier er per definisjon en flatt organisasjonsstruktur. For slike tilfeller må du gå tilbake til det tradisjonelle subclassing-paradigmet.

Det er også viktig å merke seg at en kategori ikke kan legge til forekomstvariabler. Dette betyr at du ikke kan erklære nye egenskaper i en kategori, da de bare kunne syntetiseres i hovedimplementeringen. I tillegg til at en kategori teknisk har tilgang til sine klassers forekomstvariabler, er det bedre å få tilgang til dem gjennom deres offentlige grensesnitt for å skjerme kategorien fra potensielle endringer i hovedimplementeringsfilen.


utvidelser

utvidelser (også kalt klassen utvidelser) er en spesiell type kategori som krever at deres metoder skal defineres i hoved- implementeringsblokk for den tilknyttede klassen, i motsetning til en implementering definert i en kategori. Dette kan brukes til å overstyre offentlig deklarerte egenskapsattributter. For eksempel er det noen ganger praktisk å endre en skrivebeskyttet eiendom til en skrivebeskyttet eiendom i en klasses implementering. Vurder det normale grensesnittet for a Skip klasse:

Inkludert kodeeksempel: Extensions

// Ship.h #import  #import "Person.h" @interface Skip: NSObject @property (strong, readonly) Person * kaptein; - (id) initWithCaptain: (Person *) kaptein; @slutt

Det er mulig å overstyre @eiendom definisjon inne i en klassen utvidelse. Dette gir deg muligheten til å deklarere eiendommen som Les Skriv i implementeringsfilen. Syntaktisk ser en utvidelse ut som en tom kategorierklæring:

// Ship.m #import "Ship.h" // Klassen forlengelsen. @interface Ship () @property (strong, readwrite) Person * kaptein; @end // Standard implementering. @implementation Ship @synthesize captain = _captain; - (id) initWithCaptain: (Person *) kaptein self = [super init]; hvis (selv) // Dette vil fungere på grunn av utvidelsen. [selvsettende kaptein: kaptein];  returner selv;  @slutt

Legg merke til () lagt til klassenavnet etter @interface direktiv. Dette er hva som markerer det som en forlengelse snarere enn et normalt grensesnitt eller en kategori. Eventuelle egenskaper eller metoder som vises i utvidelsen bli erklært i hovedimplementeringsblokken for klassen. I dette tilfellet legger vi ikke til noen nye felt - vi overstyrer en eksisterende. Men i motsetning til kategorier, utvidelser kan legg til ekstra instansvariabler i en klasse, og derfor kan vi deklarere egenskaper i en klasseutvidelse, men ikke en kategori.

Fordi vi re-erklært kaptein eiendom med a Les Skriv attributt, initWithCaptain: Metoden kan bruke setCaptain: tilgang på seg selv. Hvis du skulle slette utvidelsen, ville eiendommen gå tilbake til sin skrivebeskyttet status og kompilatoren ville klage. Klienter som bruker Skip klassen er ikke ment å importere implementasjonsfilen, så den kaptein Eiendommen forblir skrivebeskyttet.

#importere  #import "Person.h" #import "Ship.h" int main (int argc, const char * argv []) @ autoreleasepool Person * heywood = [[Personallokering] init]; heywood.name = @ "Heywood"; Skip * discoveryOne = [[Ship alloc] initWithCaptain: heywood]; NSLog (@ "% @", [discoveryOne kaptein] .navn); Person * dave = [[Personallokering] init]; dave.name = @ "Dave"; // Dette vil IKKE fungere fordi eiendommen fortsatt er skrivebeskyttet. [discoveryOne sattCaptain: dave];  returnere 0; 

Private metoder

Et annet vanlig bruk tilfelle for utvidelser er for å erklære private metoder. I det forrige kapitlet så vi hvordan private metoder kan deklareres ved ganske enkelt å legge dem overalt i implementeringsfilen. Men før Xcode 4.3 var dette ikke tilfelle. Den kanoniske måten å lage en privat metode var å videresende - erklære den ved hjelp av en klasseutvidelse. La oss ta en titt på dette ved å endre litt Skip header fra forrige eksempel:

// Ship.h #import  @ Interface Ship: NSObject - (void) shoot; @slutt

Deretter skal vi gjenskape eksemplet vi brukte da vi diskuterte private metoder i Metoder kapittel. I stedet for bare å legge til privat prepareToShoot Metode for implementering, vi må videresende - erklære det i en klasseutvidelse.

// Ship.m #import "Ship.h" // Klassen forlengelsen. @interface Ship () - (void) prepareToShoot; @end // Resten av implementeringen. @implementation Ship BOOL _gunIsReady;  - (void) shoot if (! _gunIsReady) [self prepareToShoot]; _gunIsReady = JA;  NSLog (@ "Firing!");  - (void) prepareToShoot // Utfør noen privat funksjonalitet. NSLog (@ "Forbereder hovedvåpenet ...");  @slutt

Kompilatoren sikrer at utvidelsesmetodene implementeres i hovedimplementeringsblokken, og derfor virker det som en fordeklarasjon. Likevel, fordi utvidelsen er innkapslet i implementasjonsfilen, bør andre gjenstander aldri vite om det, noe som gir oss en annen måte å etterligne private metoder på. Mens nyere kompilatorer sparer deg for dette problemet, er det fortsatt viktig å forstå hvordan klassetillegg fungerer, siden det har vært en vanlig måte å utnytte private metoder i Objective-C-programmer til helt nylig.


Sammendrag

Dette kapitlet dekket to av de mer unike konseptene i Objective-C programmeringsspråk: kategorier og utvidelser. Kategorier er en måte å forlenge API til eksisterende klasser, og utvidelser er en måte å legge til nødvendig metoder til API utenfor hovedgrensesnittfilen. Begge disse ble opprinnelig designet for å lette byrden på å opprettholde store kodebaser.

Neste kapittel fortsetter vår reise gjennom Objective-Cs organisasjonsstrukturer. Vi lærer å definere en protokoll, som er et grensesnitt som kan implementeres av en rekke klasser.

Denne leksjonen representerer et kapittel fra Objective-C Succinctly, en gratis eBok fra teamet på Syncfusion.