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.
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:
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.
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 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.
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]
.
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.
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.
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.
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" metodLå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.
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 representationInternt 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äljareSelektorer 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.
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.
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.
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:
.
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.
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.