Objective-C Succinctly Kategorier och förlängningar

Kategorier är en objektiv-C-språkfunktion som låter dig lägga till nya metoder i en befintlig klass, ungefär som C # -tillägg. Förväxla dock inte C # -tillägg med Objective-C-tillägg. Objektiv-C: s utvidgningar är ett speciellt fall av kategorier som låter dig definiera metoder som måste deklareras i huvudsakliga implementeringsblocket.

Dessa är kraftfulla funktioner som har många potentiella användningsområden. För det första gör kategorierna det möjligt att dela upp ett klasss gränssnitt och implementering i flera filer, vilket ger mycket nödvändig modulärhet för större projekt. För det andra kan kategorier låta dig fixa fel i en befintlig klass (t.ex.., NSString) utan att behöva subclass det. För det tredje tillhandahåller de ett effektivt alternativ till de skyddade och privata metoderna som finns i C # och andra Simula-liknande språk.


kategorier

en kategori är en grupp relaterade metoder för en klass, och alla metoder som definieras i en kategori finns tillgängliga via klassen som om de definierades i huvudgränssnittet. Till exempel, ta Person klass som vi har arbetat med i hela denna bok. Om detta var ett stort projekt, Person kan ha dussintals metoder som sträcker sig från grundläggande beteenden till interaktioner med andra människor att identifiera kontroller. API: n kan kräva att alla dessa metoder är tillgängliga via en enda klass, men det är mycket lättare för utvecklare att behålla om varje grupp lagras i en separat fil. Dessutom eliminerar kategorierna behovet av att kompilera hela klassen varje gång du ändrar en enda metod, vilket kan vara en tidsbesparare för mycket stora projekt.

Låt oss ta en titt på hur kategorier kan användas för att uppnå detta. Vi börjar med ett normalt klassgränssnitt och ett motsvarande genomförande:

// Person.h @ interface Person: NSObject @interface Person: NSObject @property (readonly) NSMutableArray * vänner; @property (copy) NSString * namn; - (tom) sägHello; - (tomrum) sägGodbye; @end // Person.m #import "Person.h" @implementation Person @synthesize name = _name; @synthesize friends = _friends; - (id) init self = [super init]; om (själv) _friends = [[NSMutableArray alloc] init];  återvänd själv  - (void) sayHello NSLog (@ "Hej, säger% @.", _name);  - (void) sayGoodbye NSLog (@ "Farväl, säger% @.", _name);  @slutet

Inget nytt här-bara en Person klass med två egenskaper (the vänner egendom kommer att användas av vår kategori) och två metoder. Därefter använder vi en kategori för att lagra vissa metoder för att interagera med andra Person instanser. Skapa en ny fil, men istället för en klass, använd Objektiv-C-kategori mall. Använda sig av relationer för kategorinamnet och Person för Kategori på fält:

Skapa klassen Person + Relationer

Som förväntat kommer detta att skapa två filer: en rubrik för att hålla gränssnittet och en implementering. Men dessa kommer båda att se lite annorlunda ut än vad vi har jobbat med. Låt oss först titta på gränssnittet:

// Person + Relations.h #import  #import "Person.h" @interface Person (Relationer) - (void) addFriend: (Person *) aFriend; - (tom) removeFriend: (Person *) aFriend; - (tom) sayHelloToFriends; @slutet

Istället för det normala @gränssnitt deklaration, vi inkluderar kategorinamnet inom parentes efter det klassnamn vi utökar. Ett kategorinamn kan vara något, så länge det inte strider mot andra kategorier för samma klass. En kategori fil namn ska vara klassnamnet följt av ett plustecken följt av kategorinamn (t.ex.., Person + Relations.h ).

Så definierar detta kategorins gränssnitt. Alla metoder vi lägger till här läggs till i originalet Person klass vid körning. Det kommer att verka som om Lägg till vän:, ta bort vän:, och sayHelloToFriends metoder är alla definierade i Person.h, men vi kan hålla vår funktionalitet inkapslad och underhållbar. Observera också att du måste importera rubriken för den ursprungliga klassen, Person.h. Kategori implementeringen följer ett liknande mönster:

// Person + Relations.m #import "Person + Relations.h" @implementation Person (Relationer) - (void) addFriend: (Person *) aFriend [[self friends] addObject: aFriend];  - (void) removeFriend: (Person *) aFriend [[self friends] removeObject: aFriend];  - (void) sayHelloToFriends for (Person * vän i [självvänner]) NSLog (@ "Hej där,% @!", [vännamn]);   @slutet

Detta implementerar alla metoder i Person + Relations.h. Precis som kategorins gränssnitt visas kategorinamnet inom parentes efter klassnamnet. Kategorinamnet i genomförandet ska matcha det i gränssnittet.

Observera också att det inte finns något sätt att definiera ytterligare egenskaper eller instansvariabler i en kategori. Kategorier måste hänvisa till data lagrade i huvudklassen (vänner i det här fallet).

Det är också möjligt att åsidosätta genomförandet som finns i Person.m genom att helt enkelt omdefiniera metoden i Person + Relations.m. Detta kan användas för att apa lappa en befintlig klass; Det rekommenderas dock inte om du har en alternativ lösning på problemet eftersom det inte finns något sätt att åsidosätta implementeringen definierad av kategorin. Det vill säga till skillnad från klasshierarkin är kategorier en platt organisationsstruktur. Om du implementerar samma metod i två separata kategorier är det omöjligt för runtime att ta reda på vilken som ska användas.

Den enda förändring du måste göra för att använda en kategori är att importera kategorins huvudfiler. Som du kan se i följande exempel, Person klassen har tillgång till de metoder som definieras i Person.h tillsammans med de som definieras i kategorin 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];  returnera 0; 

Och det är allt som finns att skapa kategorier i Objective-C.

Skyddade metoder

Att upprepa, Allt Mål-C-metoder är offentliga - det finns ingen språkkonstruktion för att markera dem som antingen privata eller skyddade. Istället för att använda "sanna" skyddade metoder kan mål-C-program kombinera kategorier med gränssnittet / genomförandeparadigmet för att uppnå samma resultat.

Tanken är enkel: deklarera "skyddade" metoder som en kategori i en separat headerfil. Detta ger underklasser möjligheten att "välja" i de skyddade metoderna, medan orelaterade klasser använder den "offentliga" headerfilen som vanligt. Ta till exempel en standard Fartyg gränssnitt:

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

Som vi sett många gånger definierar detta en offentlig metod som heter skjuta. Att deklarera a skyddad metod, vi behöver skapa en Fartyg kategori i en dedikerad headerfil:

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

Alla klasser som behöver tillgång till de skyddade metoderna (nämligen superklassen och några underklasser) kan enkelt importera Ship_Protected.h. Till exempel, Fartyg genomförandet ska definiera ett standardbeteende för den skyddade metoden:

// Ship.m #import "Ship.h" #import "Ship_Protected.h" @implementation Ship BOOL _gunIsReady;  - (void) shoot if (! _gunIsReady) [självberedandeToShoot]; _gunIsReady = JA;  NSLog (@ "Firing!");  - (void) prepareToShoot // Utför vissa privata funktionaliteter. NSLog (@ "Förbereda huvudvapnet ...");  @slutet

Observera att om vi inte hade importerat Ship_Protected.h, detta prepareToShoot genomförandet skulle vara en privat metod, som diskuteras i Metoder kapitel. Utan en skyddad kategori skulle det inte vara möjligt för underklasser att komma åt denna metod. Låt oss subklassera Fartyg för att se hur det fungerar. Vi ringer det ResearchShip:

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

Det här är ett vanligt underklassgränssnitt inte Importera den skyddade rubriken, eftersom det skulle göra de skyddade metoderna tillgängliga för alla som importerar ResearchShip.h, vilket är precis vad vi försöker undvika. Slutligen importerar implementeringen för underklassen de skyddade metoderna och (eventuellt) överrätter dem:

// ResearchShip.m #import "ResearchShip.h" #import "Ship_Protected.h" @implementation ResearchShip - (void) extendTelescope NSLog (@ "Utvidga teleskopet");  // Åsidosätta skyddad metod - (tom) förberedaToShoot NSLog (@ "Oh shoot! Vi måste hitta några vapen!");  @slutet

Att genomdriva de skyddade statuserna för metoderna i Ship_Protected.h, Andra klasser får inte importera det. De importerar bara de vanliga "offentliga" gränssnitten för superklass och underklass:

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

Eftersom varken main.m, Ship.h, inte heller ResearchShip.h importera de skyddade metoderna, kommer den här koden inte ha tillgång till dem. Försök lägga till en [discoveryOne prepareToShoot] metod - det kommer att kasta ett kompilatorfel, eftersom prepareToShoot deklarationen är ingenstans att hitta.

Sammanfattningsvis kan skyddade metoder emuleras genom att placera dem i en dedikerad headerfil och importera den headerfilen till implementeringsfilerna som kräver åtkomst till de skyddade metoderna. Inga andra filer ska importera den skyddade rubriken.

Medan arbetsflödet som presenteras här är ett helt giltigt organisationsverktyg, kom ihåg att Objective-C aldrig var tänkt att stödja skyddade metoder. Tänk på detta som ett alternativt sätt att strukturera en Objective-C-metod, snarare än en direkt ersättning för C # / Simula-style skyddade metoder. Det är ofta bättre att leta efter ett annat sätt att strukturera dina klasser istället för att tvinga din mål-C-kod att fungera som ett C # -program.

förbehåll

Ett av de största problemen med kategorier är att du inte på ett tillförlitligt sätt kan åsidosätta metoder som definieras i kategorier för samma klass. Om du till exempel definierade en Lägg till vän: klass i Person (förbindelser) och bestämde sig senare för att ändra Lägg till vän: genomförande via a Person (säkerhet) kategori finns det inget sätt för runtime att veta vilken metod den ska använda eftersom kategorierna per definition är en platt organisationsstruktur. För dessa sorters fall måste du återgå till det traditionella subclassing-paradigmet.

Det är också viktigt att notera att en kategori inte kan lägga till instansvariabler. Det betyder att du inte kan deklarera nya egenskaper i en kategori, eftersom de bara kunde syntetiseras i huvudimplementeringen. Dessutom, medan en kategori tekniskt har tillgång till sina klassers instansvariabler, är det bättre att få tillgång till dem via deras offentliga gränssnitt för att skydda kategorin från potentiella ändringar i huvudimplementeringsfilen.


Extensions

Extensions (även kallad klasstillägg) är en särskild typ av kategori som kräver att deras metoder definieras i huvud implementeringsblock för den tillhörande klassen, i motsats till en implementering definierad i en kategori. Detta kan användas för att åsidosätta offentligt deklarerade egenskapsattribut. Det är till exempel bekvämt att ändra en skrivskyddad egenskap till en läs- och skrivfastighet inom en klassens genomförande. Tänk på det normala gränssnittet för a Fartyg klass:

Inkluderat kodprov: Extensions

// Ship.h #import  #import "Person.h" @interface Ship: NSObject @property (starkt, personligt) Person * kapten; - (id) initWithCaptain: (Person *) kapten; @slutet

Det är möjligt att åsidosätta @fast egendom definition inuti en klassförlängning. Detta ger dig möjlighet att omklara fastigheten som läsa skriva i implementeringsfilen. Syntaktiskt ser en förlängning ut som en tom kategorideklaration:

// Ship.m #import "Ship.h" // Klassutvidgningen. @interface Ship () @property (strong, readwrite) Person * kapten; @end // Standard implementeringen. @implementation Ship @synthesize captain = _captain; - (id) initWithCaptain: (Person *) kapten self = [super init]; om (själv) // Detta kommer att fungera på grund av förlängningen. [självständig kapten: kaptenen];  återvänd själv  @slutet

Notera () bifogas klassnamnet efter @gränssnitt direktiv. Det här markerar det som en förlängning snarare än ett normalt gränssnitt eller en kategori. Eventuella egenskaper eller metoder som visas i tillägget måste förklaras i det huvudsakliga genomförandeblocket för klassen. I det här fallet lägger vi inte till några nya fält - vi överträder en befintlig. Men till skillnad från kategorier, tillägg kan lägg till extra instansvariabler i en klass, varför vi kan deklarera egenskaper i en klassförlängning men inte en kategori.

Eftersom vi omdeklarerade kapten egendom med a läsa skriva attribut, initWithCaptain: Metoden kan använda setCaptain: accessor på sig själv. Om du skulle radera tillägget skulle egenskapen återgå till läsningsstatus och kompilatorn skulle klaga. Kunder som använder Fartyg klassen ska inte importera implementeringsfilen, så kapten egendom kommer att förbli skrivskyddad.

#importera  #import "Person.h" #import "Ship.h" int main (int argc, const char * argv []) @autoreleasepool Person * heywood = [[Personallokering] init]; heywood.name = @ "Heywood"; Fartyg * discoveryOne = [[Ship alloc] initWithCaptain: heywood]; NSLog (@ "% @", [discoveryOne captain] .name); Person * dave = [[Personallokering] init]; dave.name = @ "Dave"; // Det här kommer inte att fungera eftersom fastigheten fortfarande är skrivskyddad. [discoveryOne setCaptain: dave];  returnera 0; 

Privata metoder

Ett annat vanligt fall för förlängningar är att deklarera privata metoder. I det föregående kapitlet såg vi hur privata metoder kan förklaras genom att helt enkelt lägga till dem var som helst i implementeringsfilen. Men före Xcode 4.3 var det inte så. Det kanoniska sättet att skapa en privat metod var att vidarebefordra - förklara det med en klasstillägg. Låt oss ta en titt på detta genom att ändra Fartyg rubrik från föregående exempel:

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

Därefter kommer vi att återskapa det exempel vi använde när vi diskuterade privata metoder i Metoder kapitel. I stället för att helt enkelt lägga till den privata prepareToShoot metod för genomförandet måste vi vidarebefordra det i en klassförlängning.

// Ship.m #import "Ship.h" // Klassutvidgningen. @interface Ship () - (void) prepareToShoot; @end // Resten av genomförandet. @implementation Ship BOOL _gunIsReady;  - (void) shoot if (! _gunIsReady) [självberedandeToShoot]; _gunIsReady = JA;  NSLog (@ "Firing!");  - (void) prepareToShoot // Utför vissa privata funktionaliteter. NSLog (@ "Förbereda huvudvapnet ...");  @slutet

Kompilatorn ser till att utvidgningsmetoderna implementeras i huvudimplementeringsblocket, varför det fungerar som en fördeklaration. Men eftersom utvidgningen är inkapslad i implementeringsfilen, borde andra objekt aldrig någonsin veta om det, vilket ger oss ett annat sätt att emulera privata metoder. Medan nyare kompilatorer räddar dig för detta problem är det fortfarande viktigt att förstå hur klassförlängningar fungerar, eftersom det har varit en vanlig metod att utnyttja privata metoder i mål-C-program fram till väldigt nyligen.


Sammanfattning

Detta kapitel omfattade två av de mer unika koncepten i målspråket Objective-C: kategorier och tillägg. Kategorier är ett sätt att förlänga API för befintliga klasser, och tillägg är ett sätt att lägga till nödvändig metoder till API utanför huvudgränssnittet. Båda dessa var ursprungligen utformade för att underlätta bördan för att upprätthålla stora kodbaser.

Nästa kapitel fortsätter vår resa genom Objective-Cs organisationsstrukturer. Vi lär oss att definiera ett protokoll, vilket är ett gränssnitt som kan implementeras av en mängd olika klasser.

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