Mål-C Succinctly Metoder

I det här kapitlet utforskar vi objektiv-C-metoder i mycket mer detalj än vad vi har i tidigare kapitel. Detta inkluderar en djupgående diskussion av exempel metoder, klassmetoder, viktiga inbyggda metoder, arv, namnkonventioner och gemensamma designmönster.


Instans vs klassmetoder

Vi har arbetat med både exempel och klassmetoder genom hela denna bok, men låt oss ta en stund att formalisera de två huvudkategorierna av metoder i mål-C:

  • Instansmetoder - Funktioner som är bundna till ett objekt. Instansmetoder är "verben" som är associerade med ett objekt.
  • Klassmetoder - Funktioner kopplade till själva klassen. De kan inte användas av fall i klassen. Dessa liknar statiska metoder i C #.

Som vi har sett många gånger, nämns instansmetoder av en bindestreck före metoden, medan klassmetoder är prefixade med ett plustecken. Till exempel, låt oss ta en förenklad version av vår Person.h fil:

@interface Person: NSObject @property (copy) NSString * namn; - (tom) sägHello; + (Person *) person med namn: (NSString *) namn; @slutet

På samma sätt måste motsvarande implementeringsmetoder föregås av en bindestreck eller ett plustecken. Så, en minimal Person.m kan se något ut som:

#import "Person.h" @implementation Person @synthesize name = _name; - (void) sayHello NSLog (@ "HELLO");  + (Person *) person med namn: (NSString *) namn Person * person = [[Personallokering] init]; person.name = namn; återvändande person;  @slutet

De Säg hej Metoden kan kallas av instanser av Person klass, medan personWithName Metoden kan bara ringas av klassen själv:

Person * p1 = [Person person med namn: @ "Frank"]; // Klass metod. [p1 sayHello]; // Instansmetod.

Det mesta av detta borde vara bekant för dig nu, men nu har vi möjlighet att prata om några av de unika konventionerna i Mål-C.


Super-sökordet

I någon objektorienterad miljö är det viktigt att kunna komma åt metoder från föräldraklassen. Objective-C använder ett mycket liknande system till C #, förutom istället för bas, det använder super nyckelord. Till exempel följande genomförande av Säg hej skulle visa HEJ på utmatningspanelen och ring sedan föräldraklassens version av Säg hej:

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

Till skillnad från i C # behöver överstyrningsmetoder inte uttryckligen markeras som sådana. Du ser detta med både i det och dealloc metoder som diskuteras i följande avsnitt. Även om dessa definieras på NSObject klass, klagar kompilatorn inte när du skapar din egen i det och dealloc metoder i underklasser.


Initialiseringsmetoder

Initialiseringsmetoder krävs för alla objekt. Ett nytt tilldelat objekt anses inte vara "redo att användas" tills en av dess initialiseringsmetoder har blivit kallad. De är stället för att ställa in standardvärden, till exempel variabler och på annat sätt ställa in objektets tillstånd. De NSObject klassen definierar en standard i det metod som inte gör någonting, men det är ofta användbart att skapa din egen. Till exempel en anpassad i det genomförande för vår Fartyg klassen kan tilldela standardvärden till en instansvariabel som heter _ammunition:

- (id) init self = [super init]; om (själv) _ammo = 1000;  återvänd själv 

Detta är det kanoniska sättet att definiera en anpassad i det metod. De själv sökord motsvarar C # s detta-det brukar hänvisa till förekomsten som kallar metoden, vilket gör det möjligt för ett objekt att skicka meddelanden till sig själv. Som du kan se, allt i det metoder krävs för att returnera förekomsten. Det här gör det möjligt att använda [[Ship alloc] init] syntax för att tilldela instansen till en variabel. Observera också det eftersom NSObject gränssnittet deklarerar i det metod, det är inte nödvändigt att lägga till en i det deklaration till Ship.h.

Medan det är enkelt i det metoder som den som visas i det föregående provet är användbara för att ställa in standardvärden för instansvariabel, det är ofta bekvämare att skicka parametrar till en initialiseringsmetod:

- (id) initWithAmmo: (unsigned int) theAmmo self = [super init]; om (själv) _ammo = theAmmo;  återvänd själv 

Om du kommer från en C # -bakgrund kan du vara obekväma med initWithAmmo metodnamn. Du skulle förmodligen förvänta dig att se Ammunition parametern separerad från det aktuella metoden namn som void init (uint ammo); Emellertid är Objective-C-metodnamn baserad på en helt annan filosofi.

Minns att Objective-Cs mål är att tvinga ett API att vara så beskrivande som möjligt, så att det inte finns någon förvirring om vad ett metodsamtal ska göra. Du kan inte tänka på en metod som en separat enhet från dess parametrar-de är en enda enhet. Detta designbeslut återspeglas faktiskt i Mål-C: s genomförande, vilket inte skiljer mellan en metod och dess parametrar. Internt är ett metodnamn egentligen sammanlänkad parameterlista.

Tänk på följande tre metoddeklarationer. Observera att den andra och tredje inte är inbyggda metoder för NSObject, så du do måste lägga till dem i klassens gränssnitt innan de implementeras.

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

Även om det här ser ut som överbelastning, är det tekniskt inte. Dessa är inte variationer på i det metod - de är alla helt oberoende metoder med olika metodnamn. Namnen på dessa metoder är följande:

init initWithAmmo: initWithAmmo: kapten:

Detta är anledningen till att du ser notation som indexOfObjectWithOptions: passingTest: och indexOfObjectAtIndexes: alternativ: passingTest: för att referera till metoder i den officiella Objective-C-dokumentationen (från NSArray).

Från en praktisk synvinkel betyder det att den första parametern för dina metoder alltid ska beskrivas med "primär" metodnamn. Tvetydiga metoder som följande genereras generellt av Objective-C-programmerare:

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

I stället bör du använda en preposition för att inkludera den första parametern i metodnamnet, så här:

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

Inklusive båda OtherShip och ett skepp I metoden kan definitionen tyckas redundant, men kom ihåg att ett skepp argument används endast internt. Någon som ringer metoden kommer att skriva något liknande shootOtherShip: discoveryOne, var discoveryOne är variabeln som innehåller det skepp du vill skjuta. Det här är exakt den typ av omsättning som Objective-C-utvecklare strävar efter.

Klassinitiering

Utöver i det metod för initialisering instanser, Mål-C ger också ett sätt att ställa in klasser. Innan du anropar några klassmetoder eller instanserar några objekt, ringer Objective-C runtime initialisera klassmetod för den aktuella klassen. Detta ger dig möjlighet att definiera statiska variabler innan någon använder klassen. Ett av de vanligaste användningsfallen för detta är att skapa singletoner:

statiskt skepp * _sharedShip; + (void) initialisera om (själv == [Skeppsklass]) _sharedShip = [[self alloc] init];  + (Ship *) sharedShip return _sharedShip; 

Före första gången [Ship sharedShip] kallas, runtime kommer att ringa [Ship initiera], vilket gör att singleton definieras. Den statiska variabelmodifieraren tjänar samma syfte som den gör i C # -det skapar en klassnivåvariabel istället för en instansvariabel. De initialisera Metoden kallas bara en gång, men det kallas alla superklasser, så du måste ta hand om att inte initiera variabler på klassnivå flera gånger. Det är därför vi inkluderade själv == [fartygsklass] villkorad för att vara säker _shareShip tilldelas endast i Fartyg klass.

Observera också att inuti en klassmetod, själv sökord hänvisar till klassen själv, inte en förekomst. Så, [självtilldelning] i det sista exemplet motsvarar [Ship alloc].


Avallokeringsmetoder

Den logiska motsvarigheten till en instans initialiseringsmetod är dealloc metod. Den här metoden kallas på ett objekt när referensräkningen når noll och dess underliggande minne kommer snart att fördelas.

Avallokering i MMR

Om du använder manuell minneshantering (rekommenderas inte) måste du släppa eventuella instansvariabler som ditt objekt har tilldelats i dealloc metod. Om du inte befriar instansvariabler innan ditt objekt går utom räckhåll, har du danglande pekare till dina instansvariabler, vilket innebär läckt minne när en förekomst av klassen släpps. Till exempel, om vår Fartyg klassen tilldelas en variabel som heter _pistol i dess i det metod, du måste släppa in den dealloc. Detta visas i följande exempel (Gun.h innehåller ett tomt gränssnitt som helt enkelt definierar Pistol klass):

#import "Ship.h" #import "Gun.h" @implementation Ship BOOL _gunIsReady; Gun * _gun;  - (id) init self = [super init]; om (själv) _gun = [[Gun allocation] init];  återvänd själv  - (void) dealloc NSLog (@ "Deallocating a Ship"); [_gun release]; [super dealloc];  @slutet

Du kan se dealloc metod i åtgärd genom att skapa en Fartyg och släppa den, som så:

int main (int argc, const char * argv []) @ autoreleasepool Ship * ship = [[Ship alloc] init]; [ship autorelease]; NSLog (@ "Ship ska fortfarande finnas i autoreleasepool");  NSLog (@ "Fartyg ska fördelas nu"); returnera 0; 

Detta visar också hur auto-släppta objekt fungerar. De dealloc Metoden kommer inte att ringas fram till slutet av @autoreleasepool blockera, så den tidigare koden ska mata ut följande:

Fartyg ska fortfarande existera i autoreleasepool Avallocating a Ship Ship bör fördelas nu

Observera att den första NSLog () meddelande i main () visas innan den i dealloc metod, även om det kallades efter de funktionen för automatisk ring upp.

Avallokering i ARC

Om du använder automatisk referensräkning fördelas alla dina instansvariabler automatiskt och [super dealloc] kommer också att kallas för dig (du borde aldrig kalla det explicit). Så det enda du behöver oroa dig för är icke-objektvariabler som buffertar skapade med C: er malloc ().

Tycka om i det, du behöver inte genomföra en dealloc metod om ditt objekt inte behöver någon speciell hantering innan den släpps. Detta är ofta fallet för automatiska referensräknande miljöer.


Privata metoder

En stor hinder för C # -utvecklare som övergår till Objective-C är den uppenbara bristen på privata metoder. Till skillnad från C # är alla metoder i en målgrupp C tillgängliga för tredje part. Det är dock möjligt att tävla med uppförandet av privata metoder.

Kom ihåg att klienter bara importerar gränssnittet till en klass (dvs huvudfilerna) -De borde aldrig se den underliggande implementeringen. Så, genom att lägga till nya metoder inuti genomförande fil utan att inkludera dem i gränssnitt, Vi kan effektivt dölja metoder från andra objekt. Det är dock mer konventionsbaserat än "sanna" privata metoder, men det är i princip samma funktion: försöker ringa en metod som inte deklareras i ett gränssnitt kommer att resultera i ett kompilatorfel.

Försöker ringa en "privat" metod

Låt oss säga att du behövde lägga till en privat prepareToShoot metod till Fartyg klass. Allt du behöver göra är att undvika det från Ship.h samtidigt som den läggs till Ship.m:

// Ship.h @ interface Ship: NSObject @property (svag) Person * kapten; - (tomrum) skott; @slutet

Detta förklarar en offentlig metod som heter skjuta, som kommer att använda den privata prepareToShoot metod. Motsvarande 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 vissa privata funktionaliteter. NSLog (@ "Förbereda huvudvapnet ...");  @slutet

Från Xcode 4.3 kan du definiera privata metoder var som helst i genomförandet. Om du använder den privata metoden innan kompilatorn har sett den (som i föregående exempel) kontrollerar kompilatorn resten av implementeringsblocket för metoddefinitionen. Före Xcode 4.3 måste du antingen definiera en privat metod innan den användes någon annanstans i filen, eller vidareförklaras den med en klassförlängning.

Klasstillägg är ett speciellt fall av kategorier, som presenteras i det kommande kapitlet. Precis som det inte finns något sätt att markera en metod som privat, finns det inget sätt att markera en metod som skyddad. Men som vi kommer att se i nästa kapitel, utgör kategorier ett kraftfullt alternativ till skyddade metoder.


väljare

Selektorer är Objective-C: s sätt att representera metoder. De låter dig dynamiskt "välja" ett av objektets metoder, som kan användas för att referera till en metod vid körning, överföra en metod till en annan funktion och ta reda på om ett objekt har en viss metod. För praktiska ändamål kan du tänka på en väljare som ett alternativt namn för en metod.

Utvecklares representation av en metod vs Objective-C: s representation

Internt använder Objective-C ett unikt nummer för att identifiera varje metodnamn som ditt program använder. Till exempel kallas en metod Säg hej kan översättas till 4984331082. Denna identifierare heter a väljare, och det är ett mycket effektivare sätt för kompilatorn att hänvisa till metoder än deras fullständiga strängrepresentation. Det är viktigt att förstå att en väljare endast representerar metoden namn-inte en specifik metod implementering. Med andra ord, a Säg hej Metod definierad av Person klassen har samma väljare som a Säg hej Metod definierad av Fartyg klass.

De tre viktigaste verktygen för att arbeta med selektörer är:

  • @väljare() - Returnera väljaren som är associerad med ett källkodsmetodsnamn.
  • NSSelectorFromString () - Returnera väljaren som är associerad med strängrepresentationen av ett metodnamn. Denna funktion gör det möjligt att definiera metodnamnet vid körtid, men det är mindre effektivt än @väljare().
  • NSStringFromSelector () - Returnera strängrepresentationen av ett metodnamn från en väljare.

Som du kan se finns det tre sätt att representera ett metodnamn i Objective-C: som källkod, som en sträng eller som en väljare. Dessa omvandlingsfunktioner visas grafiskt i följande figur:

Konvertera mellan källkod, strängar och väljare

Selektorer lagras i en särskild datatyp som heter SEL. Följande utdrag visar den grundläggande användningen av de tre omvandlingsfunktionerna som visas i föregående figur:

int main (int argc, const char * argv []) @ autoreleasepool SEL selector = @selector (sayHello); NSLog (@ "% @", NSStringFromSelector (selector)); om (selector == NSSelectorFromString (@ "sayHello")) NSLog (@ "Väljarna är lika!");  returnera 0; 

Först använder vi @väljare() direktiv för att räkna ut väljaren för en metod som heter Säg hej, vilken är en källkodsrepresentation av ett metodnamn. Observera att du kan skicka några metodnamn till @väljare() -det behöver inte existera någon annanstans i ditt program. Därefter använder vi NSStringFromSelector () funktionen för att omvandla väljaren tillbaka till en sträng så att vi kan visa den i utmatningspanelen. Slutligen visar den villkorliga att selektörer har en en-till-en-korrespondens med metodnamn, oavsett om du hittar dem genom hårdkodade metodenamn eller strängar.

Metodnamn och Selectors

Det föregående exemplet använder en enkel metod som inte tar några parametrar, men det är viktigt att kunna överföra metoder som do acceptera parametrar. Kom ihåg att ett metodenamn består av det primära metodenamnet som är sammanfogat med alla parameternamn. Till exempel en metod med signatur

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

skulle ha en metod namn av:

sayHelloToPerson: withGreeting:

Detta är vad du skulle vidarebefordra till @väljare() eller NSSelectorFromString () att returnera identifieraren för den metoden. Selectors arbetar bara med metod namn (inte signaturer), så det finns inte en en-till-en korrespondens mellan väljare och signaturer. Som ett resultat, metoden namn i det sista exemplet kommer också att matcha en signatur med olika datatyper, inklusive följande:

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

Omfattningen av mål-Cs namngivningskonventioner undviker mest förvirrande situationer; valörer för enparametermetoder kan dock fortfarande vara knepiga eftersom att lägga till ett kolon till metodnamnet faktiskt ändrar det till en helt annorlunda metod. Till exempel i det följande provet tar det första metodenamnet inte en parameter, medan den andra gör det:

säg hej säga hej

Återigen, namngivningskonventioner går långt för att eliminera förvirring, men du måste fortfarande se till att du vet när det är nödvändigt att lägga till ett kolon till slutet av ett metodnamn. Det här är en vanlig fråga om du är ny till väljare, och det kan vara svårt att felsöka, eftersom en bakre kolon fortfarande skapar ett helt giltigt metodnamn.

Utföra Selectors

Självklart spelar du in en väljare i en SEL variabeln är relativt meningslös utan förmåga att genomföra den senare. Eftersom en väljare är enbart en metod namn (inte ett genomförande), måste det alltid vara kopplat till ett objekt innan du kan ringa det. De NSObject klass definierar a performSelector: metod för detta ändamål.

[joe performSelector: @selector (sayHello)];

Detta motsvarar ringen Säg hej direkt på joe:

[joe sayHello];

För metoder med en eller två parametrar kan du använda relaterade performSelector: withObject: och performSelector: withObject: withObject: metoder. Följande metodimplementering:

- (void) sayHelloToPerson: (Person *) aPerson NSLog (@ "Hej,% @", [aPerson namn]); 

kan kallas dynamiskt genom att passera en person argument till performSelector: withObject: metod, som demonstreras här:

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

Detta motsvarar att passera parametern direkt till metoden:

[joe sayHelloToPerson: bill];

Likaså performSelector: withObject: withObject: Metoden låter dig skicka två parametrar till målmetoden. Den enda försiktigheten med dessa är att alla parametrar och metodens returvärde måste vara objekt-de fungerar inte med primitiva C datatyper som int, flyta, etc. Om du behöver den här funktionaliteten kan du antingen låsa den primitiva typen i en av Objective-C: s många omslagsklasser (t.ex.., NSNumber) eller använd NSInvocation-objektet för att inkapsla ett fullständigt metodsamtal.

Kontroll av att selektorer finns

Det går inte att utföra en väljare på ett objekt som inte har definierat den tillhörande metoden. Men till skillnad från statiska metallsamtal är det inte möjligt att bestämma vid kompileringstid huruvida performSelector: kommer att ge upphov till ett fel. I stället måste du kontrollera om ett objekt kan svara på en väljare vid körning med hjälp av den lämpliga namnet respondsToSelector: metod. Det återkommer helt enkelt JA eller NEJ beroende på om objektet kan utföra väljaren:

SEL methodToCall = @selector (sayHello); om ([joe respondsToSelector: methodToCall]) [joe performSelector: methodToCall];  annars NSLog (@ "Joe vet inte hur man utför% @.", NSStringFromSelector (methodToCall)); 

Om dina selektorer genereras dynamiskt (t ex om methodToCall väljs från en lista med alternativ) eller du har inte kontroll över målobjektet (t.ex.., joe kan vara en av flera olika typer av objekt), är det viktigt att köra den här kontrollen innan du försöker ringa performSelector:.

Använda Selectors

Hela tanken bakom väljare är att kunna skicka runt metoder precis som du passerar objekt. Detta kan exempelvis användas för att dynamiskt definiera en "handling" för a Person motsätta sig att genomföras senare i programmet. Tänk på följande gränssnitt:

Inkluderat kodprov: Selectors

@interface Person: NSObject @property (copy) NSString * namn; @property (svag) Person * vän; @property SEL-åtgärd; - (tom) sägHello; - (tomrum) sägGodbye; - (void) coerceFriend; @slutet

Tillsammans med motsvarande genomförande:

#import "Person.h" @implementation Person @synthesize name = _name; @synthesize friend = _friend; @synthesize action = _action; - (void) sayHello NSLog (@ "Hej, säger% @.", _name);  - (void) sayGoodbye NSLog (@ "Farväl, säger% @.", _name);  - (void) coerceFriend NSLog (@ "% @ håller på att göra% @ göra något.", _name, [_friend name]); [_friend performSelector: _action];  @slutet

Som du kan se, ringa till coerceFriend Metoden kommer att tvinga a annorlunda motsätta sig att utföra vissa godtyckliga åtgärder. Det här låter dig konfigurera en vänskap och ett beteende tidigt i ditt program och vänta tills en viss händelse inträffar innan du utlöser åtgärden:

#importera  #import "Person.h" NSString * askUserForAction () // I den verkliga världen skulle detta fånga några // användarinmatning för att bestämma vilken metod som ska ringas. NSString * theMethod = @ "sayGoodbye"; returnera theMethod;  int main (int argc, const char * argv []) @autoreleasepool // Skapa en person och bestäm en åtgärd som ska utföras. Person * joe = [[Personallokering] init]; joe.name = @ "Joe"; Person * Bill = [[Personallokering] init]; bill.name = @ "Bill"; joe.friend = bill; joe.action = NSSelectorFromString (askUserForAction ()); // Vänta på en händelse ... // Utför åtgärden. [joe coerceFriend];  returnera 0; 

Det här är nästan exakt hur användargränssnittskomponenter i iOS implementeras. Om du till exempel hade en knapp skulle du konfigurera den med ett målobjekt (t.ex.., vän) och en åtgärd (t.ex.., verkan). När användaren så småningom trycker på knappen kan den användas performSelector: att utföra den önskade metoden på det aktuella objektet. Tillåter både objektet och Metoden att variera oberoende ger stor flexibilitet. Knappen kan bokstavligen utföra några åtgärder med något objekt utan att ändra knappens klass på något sätt. Detta utgör också grunden för målmålsdesignmönstret, vilket är starkt beroende av IOS Succinctly följeslagare.


Sammanfattning

I detta kapitel behandlade vi exempel- och klassmetoder, tillsammans med några av de viktigaste inbyggda metoderna. Vi arbetade nära med selektorer, vilket är ett sätt att hänvisa till metodnamn som antingen källkod eller strängar. Vi förhandsgranskade också målmålsdesignmönstret, vilket är en integrerad del av programmeringen iOS och OS X.

I nästa kapitel diskuteras ett alternativt sätt att skapa privata och skyddade metoder i Objective-C.

Denna lektion representerar ett kapitel från Objective-C Succinctly, en gratis eBook från laget vid Syncfusion.