Mål-C Succinctly Metoder

I dette kapitlet vil vi utforske Mål-C metoder i mye mer detalj enn vi har i tidligere kapitler. Dette inkluderer en grundig diskusjon av eksempelmetoder, klassemetoder, viktige innebygde metoder, arv, navnekonvensjoner og vanlige designmønstre.


Instans vs Klassemetoder

Vi har jobbet med både eksempel- og klassemetoder gjennom hele denne boken, men la oss ta et øyeblikk til å formalisere de to hovedkategorier av metoder i Mål-C:

  • Instans metoder - Funksjoner bundet til en gjenstand. Instansmetoder er "verbene" knyttet til et objekt.
  • Klassemetoder - Funksjoner bundet til selve klassen. De kan ikke brukes av forekomster av klassen. Disse ligner på statiske metoder i C #.

Som vi har sett mange ganger, er instansmetoder benevnt av en bindestrek før metodenavnet, mens klassemetoder prefikses med et plustegn. For eksempel, la oss ta en forenklet versjon av vår Person.h fil:

@interface Person: NSObject @property (copy) NSString * navn; - (void) sayHello; + (Person *) person med navn: (NSString *) navn; @slutt

På samme måte må de tilsvarende implementeringsmetodene også foregå av en bindestrek eller et plustegn. Så, en minimal Person.m kan se noe ut som:

#import "Person.h" @implementation Person @synthesize navn = _name; - (void) sayHello NSLog (@ "HELLO");  + (Person *) person med navn: (NSString *) navn Person * person = [[Personallokering] init]; person.name = navn; returperson;  @slutt

De si hei Metoden kan kalles av forekomster av Person klasse, mens personWithName Metoden kan bare kalles av selve klassen:

Person * p1 = [Person person med navn: @ "Frank"]; // klasse metode. [p1 sayHello]; // Instansmetode.

Det meste av dette burde være kjent for deg nå, men nå har vi muligheten til å snakke om noen av de unike konvensjonene i Objective-C.


Super Søkeord

I ethvert objektorientert miljø er det viktig å kunne få tilgang til metoder fra foreldreklassen. Objective-C bruker en veldig lignende ordning til C #, bortsett fra i stedet for utgangspunkt, det bruker super søkeord. For eksempel, følgende implementering av si hei ville vise HALLO i utgangspanelet, og ring deretter foreldreklassens versjon av si hei:

- (void) sayHello NSLog (@ "HELLO"); [super sayHello]; 

I motsetning til C #, må overstyringsmetoder ikke eksplisitt merkes som sådan. Du ser dette med begge i det og dealloc metoder diskutert i følgende avsnitt. Selv om disse er definert på NSObject klassen, klager kompilatoren ikke når du lager din egen i det og dealloc metoder i underklasser.


Initialiseringsmetoder

Initialiseringsmetoder kreves for alle objekter - et nylig tildelt objekt betraktes ikke som "klar til bruk" før en av dens initialiseringsmetoder er blitt kalt. De er stedet for å angi standardverdier for eksempel variabler og ellers sette opp tilstanden til objektet. De NSObject Klassen definerer en standard i det metode som ikke gjør noe, men det er ofte nyttig å lage din egen. For eksempel, en egendefinert i det implementering for vår Skip klassen kan tildele standardverdier til en instansvariabel som kalles _ammo:

- (id) init self = [super init]; hvis (selv) _ammo = 1000;  returner selv; 

Dette er den kanoniske måten å definere en egendefinert på i det metode. De selv- Søkeordet er ekvivalent med C # s dette-det pleier å referere til forekomsten som kaller metoden, som gjør det mulig for et objekt å sende meldinger til seg selv. Som du kan se, alt i det Det kreves metoder for å returnere forekomsten. Dette gjør det mulig å bruke [[Ship alloc] init] syntaks for å tilordne forekomsten til en variabel. Vær også oppmerksom på at fordi NSObject grensesnitt erklærer i det metode, det er ikke nødvendig å legge til en i det erklæring til Ship.h.

Mens det er enkelt i det metoder som den som ble vist i forrige eksempel, er nyttige for å angi standard instansvariabelverdier, det er ofte mer praktisk å sende parametere til en initialiseringsmetode:

- (id) initWithAmmo: (usignert int) theAmmo self = [super init]; hvis (selv) _ammo = theAmmo;  returner selv; 

Hvis du kommer fra en C # bakgrunn, kan du være ubehagelig med initWithAmmo Metodenavn. Du vil nok forvente å se ammo parameter skilt fra det aktuelle metodenavn som void init (uint ammo); Imidlertid er mål-C-metodenavn basert på en helt annen filosofi.

Husk at Objective-Cs mål er å tvinge en API til å være så beskrivende som mulig, for å sikre at det ikke er absolutt ingen forvirring om hva en metodeanrop skal gjøre. Du kan ikke tenke på en metode som en separat enhet fra sine parametere-de er en enkelt enhet. Denne designbeslutningen er faktisk reflektert i Objective-Cs implementering, noe som ikke skiller mellom en metode og dens parametere. Internt er et metodenavn faktisk sammenkoblede parameterliste.

For eksempel, vurder følgende tre metoden deklarasjoner. Merk at den andre og tredje er ikke innebygde metoder for NSObject, så du gjøre må legge dem til klassens grensesnitt før de implementeres.

- (Id) init; - (id) initWithAmmo: (usignert int) theAmmo; - (id) initWithAmmo: (unsigned int) theAmmo kaptein: (Person *) theCaptain;

Selv om dette ser ut som overbelastning, er det ikke teknisk. Disse er ikke variasjoner på i det metode - de er alle helt uavhengige metoder med forskjellige metodenavn. Navnene på disse metodene er som følger:

init initWithAmmo: initWithAmmo: kaptein:

Dette er grunnen til at du ser notat som indexOfObjectWithOptions: passingTest: og indexOfObjectAtIndexes: alternativer: passingTest: for å referere til metoder i den offisielle Objective-C dokumentasjonen (hentet fra NSArray).

Fra et praktisk synspunkt betyr dette at den første parameteren til metodene dine alltid skal beskrives med "primær" metodenavn. Tvetydige metoder som følgende er generelt frynst av Objective-C programmerere:

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

I stedet bør du bruke en preposisjon for å inkludere den første parameteren i metodenavnet, slik som:

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

Inkludert begge OtherShip og et skip I metoden kan definisjonen virke overflødig, men husk at et skip Argument brukes kun internt. Noen som ringer metoden skal skrive noe som shootOtherShip: discoveryOne, hvor discoveryOne Er variabelen som inneholder skipet du vil skyte. Dette er akkurat den slags verbositet som Objective-C-utviklerne streber etter.

Klasseinitialisering

I tillegg til det i det metode for initialisering forekomster, Mål-C gir også en måte å sette opp klasser. Før du ringer noen klassemetoder eller instanserer objekter, ringer Objective-C-kjøretiden til initial klassemetode for den aktuelle klassen. Dette gir deg muligheten til å definere statiske variabler før noen bruker klassen. En av de vanligste brukssaken til dette er å sette opp singletoner:

statisk skip * _sharedShip; + (void) initialiser if (self == [Ship class]) _sharedShip = [[selvtillit] init];  + (Ship *) sharedShip return _sharedShip; 

Før første gang [Ship sharedShip] kalles, kjøretiden vil ringe [Ship initialiser], som sørger for at singleton er definert. Den statiske variabelen modifikatoren tjener samme formål som den gjør i C # -det oppretter en klassenivåvariabel i stedet for en instansvariabel. De initial Metoden kalles bare en gang, men det kalles på alle superklasser, så du må passe på ikke å initialisere variabler på klassenivå flere ganger. Det er derfor vi inkluderte selv == [skip klasse] betinget av å være sikker på _shareShip er bare tildelt i Skip klasse.

Legg også merke til at innsiden av en klassemetode, den selv- søkeord refererer til klassen selv, ikke en forekomst. Så, [selvtillit] i det siste eksemplet er ekvivalent av [Ship alloc].


Deallokeringsmetoder

Den logiske motparten til en instans initialiseringsmetode er dealloc metode. Denne metoden kalles på et objekt når referansetellingen når null, og det underliggende minnet er i ferd med å bli distribuert.

Deallokering i MMR

Hvis du bruker manuell minnehåndtering (ikke anbefalt), må du slippe eventuelle forekomstvariabler som objektet ditt er tildelt i dealloc metode. Hvis du ikke frigir forekomstvariabler før objektet ditt går utenfor omfanget, har du dangling pointers til instansvariablene, noe som betyr utelatt minne når en forekomst av klassen slippes ut. For eksempel, hvis vår Skip Klassen tildelt en variabel som heter _våpen i sin i det metode, du må slippe den inn dealloc. Dette er demonstrert i følgende eksempel (Gun.h inneholder et tomt grensesnitt som bare definerer Våpen klasse):

#import "Ship.h" #import "Gun.h" @implementation Ship BOOL _gunIsReady; Gun * _gun;  - (id) init self = [super init]; hvis (selv) _gun = [[Gun alloc] init];  returner selv;  - (void) dealloc NSLog (@ "Deallocating a Ship"); [_gun release]; [super dealloc];  @slutt

Du kan se dealloc metode i aksjon ved å opprette en Skip og frigjøre det slik:

int main (int argc, const char * argv []) @ autoreleasepool Ship * ship = [[Ship alloc] init]; [skip autorelease]; NSLog (@ "Ship bør fortsatt finnes i autoreleasepool");  NSLog (@ "Skipet skal distribueres nå"); returner 0; 

Dette viser også hvordan autoutgitte objekter fungerer. De dealloc Metoden vil ikke bli kalt til slutten av @autoreleasepool blokkere, så den forrige koden skal sende følgende:

Skipet bør fortsatt finnes i autoreleasepool Avallocating a Ship Ship bør distribueres nå

Legg merke til at den første NSLog () melding i hoved() er vist før den ene i dealloc metode, selv om det ble kalt etter de autoutgivelses anrop.

Deallocation i ARC

Men hvis du bruker automatisk referansetelling, blir alle dine instansvariabler deallokert automatisk, og [super dealloc] vil bli kalt for deg også (du bør aldri kalle det eksplisitt). Så det eneste du trenger å bekymre deg for er ikke-objektvariabler som buffere opprettet med C malloc ().

Som i det, du trenger ikke å implementere en dealloc metode hvis objektet ikke trenger noen spesiell håndtering før den slippes ut. Dette er ofte tilfelle for automatiske referanse telling miljøer.


Private metoder

En stor hindring for C # utviklere som overgår til Objective-C er den åpenbare mangelen på private metoder. I motsetning til C # er alle metoder i en Objective-C-klasse tilgjengelig for tredjeparter; Det er imidlertid mulig å etterligne oppførselen til private metoder.

Husk at klienter bare importerer grensesnittet til en klasse (det vil si topptekstfiler) -de aldri se den underliggende implementeringen. Så, ved å legge til nye metoder inne i gjennomføring fil uten å inkludere dem i grensesnitt, Vi kan effektivt skjule metoder fra andre objekter. Selv om dette er mer konvensjonsbasert enn "ekte" private metoder, er det i det vesentlige samme funksjonalitet: Å prøve å kalle en metode som ikke er erklært i et grensesnitt, vil resultere i en kompilatorfeil.

Forsøker å ringe en "privat" metode

For eksempel, la oss si at du trengte å legge til en privat prepareToShoot metode til Skip klasse. Alt du trenger å gjøre er å slippe det fra Ship.h mens du legger den til Ship.m:

// Ship.h @ Interface Ship: NSObject @property (svak) Person * kaptein; - (tom) skyte; @slutt

Dette erklærer en offentlig metode kalt skyte, som vil bruke den private prepareToShoot metode. Tilsvarende implementering kan se ut som:

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

Fra Xcode 4.3 kan du definere private metoder hvor som helst i gjennomføringen. Hvis du bruker den private metoden før kompilatoren har sett den (som i forrige eksempel), kontrollerer kompilatoren resten av implementeringsblokken for metodedefinisjonen. Før Xcode 4.3 måtte du enten definere en privat metode før den ble brukt andre steder i filen, eller fremover-erklære den med a klasse forlengelse.

Klasseutvidelser er et spesielt tilfelle av kategorier, som presenteres i det kommende kapittelet. Akkurat som det ikke er mulig å merke en metode som privat, er det ikke mulig å merke en metode som beskyttet. Men som vi vil se i neste kapittel, gir kategorier et kraftig alternativ til beskyttede metoder.


velgere

Selektorer er Objective-Cs måte å representere metoder på. De lar deg dynamisk "velge" en av metodens metoder, som kan brukes til å referere til en metode ved kjøretid, sende en metode til en annen funksjon, og finne ut om en gjenstand har en bestemt metode. For praktiske formål kan du tenke på en velger som et alternativt navn for en metode.

Utvikleres representasjon av en metode vs. Objective-Cs representasjon

Internt bruker Objective-C et unikt nummer for å identifisere hvert metodnavn som programmet bruker. For eksempel kalles en metode si hei kan oversette til 4984331082. Denne identifikatoren kalles a velger, og det er en mye mer effektiv måte for kompilatoren å referere til metoder enn deres fullstrengsrepresentasjon. Det er viktig å forstå at en velger bare representerer metoden Navn-ikke en bestemt metodeimplementering. Med andre ord, a si hei metode definert av Person klassen har samme velger som en si hei metode definert av Skip klasse.

De tre hovedverktøyene for å jobbe med selektorer er:

  • @selector () - Returner väljeren som er tilknyttet et kildekode-metodenavn.
  • NSSelectorFromString () - Returner väljeren som er knyttet til strengrepresentasjonen av et metodenavn. Denne funksjonen gjør det mulig å definere metodenavnet på kjøretid, men det er mindre effektivt enn @selector ().
  • NSStringFromSelector () - Returnere strengrepresentasjonen av et metodenavn fra en velger.

Som du kan se, er det tre måter å representere et metodenavn på i Objective-C: som kildekode, som en streng eller som en väljare. Disse konverteringsfunksjonene vises grafisk i følgende figur:

Konvertere mellom kildekode, strenge og selektorer

Selektorer lagres i en spesiell datatype som kalles SEL. Følgende utdrag demonstrerer den grunnleggende bruken av de tre konverteringsfunksjonene som vises i forrige figur:

int main (int argc, const char * argv []) @ autoreleasepool SEL selector = @selector (sayHello); NSLog (@ "% @", NSStringFromSelector (selector)); hvis (selector == NSSelectorFromString (@ "sayHello")) NSLog (@ "Seleksjonene er like!");  returnere 0; 

Først bruker vi @selector () direktiv for å finne ut selgeren for en metode som kalles si hei, som er en kildekode representasjon av et metode navn. Merk at du kan passere noen Metodenavn til @selector () -det trenger ikke eksistere andre steder i programmet. Deretter bruker vi NSStringFromSelector () funksjon for å konvertere selectoren tilbake til en streng slik at vi kan vise den i utgangspanelet. Til slutt viser det betingede at selektorer har en en-til-en-korrespondanse med metodenavn, uansett om du finner dem gjennom hardkodede metodenavn eller strenger.

Metode navn og selektorer

Det forrige eksempelet bruker en enkel metode som ikke tar noen parametre, men det er viktig å kunne overføre metoder som gjøre aksepter parametere. Husk at et metodenavn består av det primære metodenavnet sammenføyet med alle parameternavnene. For eksempel, en metode med signatur

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

ville ha en metode Navn av:

sayHelloToPerson: withGreeting:

Dette er hva du vil passere til @selector () eller NSSelectorFromString () å returnere identifikatoren for den metoden. Selektorer fungerer bare med metode navnene (ikke signaturer), så det er ikke en en-til-en korrespondanse mellom selektorer og signaturer. Som et resultat, metoden Navn i det siste eksemplet vil også matche en signatur med forskjellige datatyper, inkludert følgende:

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

Ulempen av Objective-Cs navnekonvensjoner unngår mest forvirrende situasjoner; Selektorer for enparametermetoder kan imidlertid fortsatt være vanskelige fordi ved å legge til et kolon til metodenavnet, endres det faktisk til en helt annerledes metode. For eksempel, i den følgende prøven, tar det første metodenavnet ikke en parameter, mens den andre gjør:

si hei si hei

Igjen, navnekonvensjoner går langt i retning av å eliminere forvirring, men du må fortsatt sørge for at du vet når det er nødvendig å legge til et kolon til slutten av et metodenavn. Dette er et vanlig problem hvis du er ny til selektorer, og det kan være vanskelig å feilsøke, da et bakre kolon fortsatt oppretter et helt gyldig metodenavn.

Utføre Selectors

Selvfølgelig registrerer du en velger i en SEL variabel er relativt ubrukelig uten evnen til å utføre det senere. Siden en velger er bare en metode Navn (ikke en implementering), må den alltid være sammenkoblet med et objekt før du kan ringe det. De NSObject klassen definerer a performSelector: metode for dette formålet.

[joe performSelector: @selector (sayHello)];

Dette er ekvivalent med å ringe si hei direkte på joe:

[joe sayHello];

For metoder med en eller to parametere, kan du bruke relaterte performSelector: withObject: og performSelector: withObject: withObject: metoder. Følgende metode implementering:

- (ugyldig) sayHelloToPerson: (Person *) aPerson NSLog (@ "Hei,% @", [aPersonnavn]); 

kan kalles dynamisk ved å passere en person argument til performSelector: withObject: metode, som vist her:

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

Dette tilsvarer at parameteren sendes direkte til metoden:

[joe sayHelloToPerson: bill];

På samme måte, performSelector: withObject: withObject: Metoden lar deg passere to parametere til målmetoden. Den eneste advarselen med disse er at alle parametere og returverdien til metoden må være objekter - de virker ikke med primitive C datatyper som int, flyte, etc. Hvis du trenger denne funksjonaliteten, kan du enten legge den primitive typen i en av Objective-Cs mange innpakningsklasser (f.eks.., NSNumber) eller bruk NSInvocation-objektet til å inkapslere en komplett metallsamtale.

Kontrollerer at selektorer eksisterer

Det er ikke mulig å utføre en velger på et objekt som ikke har definert den tilknyttede metoden. Men i motsetning til statiske metallsamtaler er det ikke mulig å bestemme på kompileringstid om performSelector: vil øke en feil. I stedet må du sjekke om et objekt kan svare på en velger ved kjøretid ved hjelp av den passende navnet respondsToSelector: metode. Det kommer rett og slett tilbake JA eller NEI avhengig av om objektet kan utføre väljeren:

SEL metodeToCall = @selektor (sayHello); hvis ([joe respondsToSelector: methodToCall]) [joe performSelector: methodToCall];  ellers NSLog (@ "Joe vet ikke hvordan å utføre% @.", NSStringFromSelector (methodToCall)); 

Hvis dine selektorer blir dynamisk generert (for eksempel hvis methodToCall er valgt fra en liste over alternativer), eller du har ikke kontroll over målobjektet (f.eks., joe kan være en av flere forskjellige typer objekter), er det viktig å kjøre denne sjekken før du prøver å ringe performSelector:.

Bruke selektorer

Hele ideen bak selectors er å kunne passere rundt metoder akkurat som du passerer rundt objekter. Dette kan for eksempel brukes til å dynamisk definere en "handling" for a Person motsette seg å utføre senere i programmet. For eksempel, vurder følgende grensesnitt:

Inkludert kodeeksempel: Selektorer

@interface Person: NSObject @property (copy) NSString * navn; @property (svak) Person * venn; @property SEL handling - (void) sayHello; - (void) sayGoodbye; - (void) coerceFriend; @slutt

Sammen med tilsvarende implementering:

#import "Person.h" @implementation Person @synthesize navn = _name; @synthesize friend = _friend; @synthesize action = _action; - (void) sayHello NSLog (@ "Hei, sier% @.", _name);  - (void) sayGoodbye NSLog (@ "Farvel, sier% @.", _name);  - (void) coerceFriend NSLog (@ "% @ skal gjøre% @ gjøre noe.", _name, [_friend name]); [_friend performSelector: _action];  @slutt

Som du kan se, ringer du coerceFriend metoden vil tvinge a forskjellig motsette seg å utføre noen vilkårlig handling. Dette lar deg konfigurere et vennskap og en oppførsel tidlig i programmet og vente på at en bestemt hendelse skal oppstå før du utløser handlingen:

#importere  #import "Person.h" NSString * askUserForAction () // I den virkelige verden vil dette fange noen // brukerinngang for å finne ut hvilken metode som skal ringes. NSString * theMethod = @ "sayGoodbye"; return theMethod;  int main (int argc, const char * argv []) @ autoreleasepool // Opprett en person og avgjøre en handling som skal utføres. Person * joe = [[Personallokering] init]; joe.name = @ "Joe"; Person * bill = [[Personallokering] init]; bill.name = @ "Bill"; joe.friend = bill; joe.action = NSSelectorFromString (askUserForAction ()); // Vent på en hendelse ... // Utfør handlingen. [joe coerceFriend];  returnere 0; 

Dette er nesten nøyaktig hvordan brukergrensesnittkomponenter i iOS implementeres. Hvis du for eksempel hadde en knapp, ville du konfigurere den med et målobjekt (f.eks., venn), og en handling (f.eks., handling). Da, når brukeren til slutt trykker på knappen, kan den bruke performSelector: å utføre ønsket metode på det aktuelle objektet. Tillater både objektet og Metoden for å variere uavhengig gir betydelig fleksibilitet - knappen kan bokstavelig talt utføre enhver handling med noe objekt uten å endre knappens klasse på noen måte. Dette danner også grunnlaget for mål-handlingsdesignmønsteret, som er sterkt avhengig av i iOS Succinctly følgesvenn bok.


Sammendrag

I dette kapittelet dekket vi eksempler og klassemetoder, sammen med noen av de viktigste innebygde metodene. Vi jobbet tett med selektorer, som er en måte å referere til metodenavn som enten kildekode eller strenger. Vi har også kort forhåndsvisning av Target-Action designmønsteret, som er et integrert aspekt av IOS og OS X programmering.

I neste kapittel diskuteres en alternativ måte å lage private og beskyttede metoder i Objective-C.

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