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.
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:
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.
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 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.
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]
.
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.
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.
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.
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" metodeFor 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.
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 representasjonInternt 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 selektorerSelektorer 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.
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.
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.
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:
.
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.
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.