Mål-C Succinctly Protokoll

I mål-C, a protokoll är en grupp metoder som kan genomföras av någon klass. Protokoll är i huvudsak desamma som gränssnitt i C #, och de båda har liknande mål. De kan användas som en pseudodatatyp, vilket är användbart för att se till att ett dynamiskt typat objekt kan svara på en viss uppsättning meddelanden. Och eftersom någon klass kan "anta" ett protokoll kan de användas för att representera ett delat API mellan helt orelaterade klasser.

Den officiella dokumentationen diskuterar både en informell och en formell metod för att deklarera protokoll, men informella protokoll är egentligen bara en unik användning av kategorier och ger inte nästan lika många fördelar som formella protokoll. Med detta i åtanke fokuserar detta kapitel uteslutande på formell protokoll.


Skapa ett protokoll

Låt oss först titta på hur man förklarar ett formellt protokoll. Skapa en ny fil i Xcode och välj objektiv-C-protokollikonen under Mac OS X> Kakao:

Xcode-ikonen för protokollfiler

Som vanligt kommer det att leda till ett namn. Vårt protokoll innehåller metoder för att beräkna ett objekts koordinater, så låt oss ringa det CoordinateSupport:

Namngivna protokollet

Klick Nästa och välj standardplatsen för filen. Detta skapar ett tomt protokoll som ser nästan ut som ett gränssnitt:

// CoordinateSupport.h #import  @protocol CoordinateSupport  @slutet

Självklart, istället för @gränssnitt direktivet använder den @protokoll, följt av protokollets namn. De syntax låter oss införliva ett annat protokoll till CoordinateSupport. I det här fallet säger vi det CoordinateSupport innehåller också alla metoder som deklarerats i NSObject protokoll (inte förväxlas med NSObject klass).

Låt oss sedan lägga till några metoder och egenskaper för protokollet. Detta fungerar på samma sätt som deklarerar metoder och egenskaper i ett gränssnitt:

#importera  @protocol CoordinateSupport  @property double x; @property double y; @property double z; - (NSArray *) arrayFromPosition; - (dubbel) magnitude; @slutet

Anta ett protokoll

Alla klasser som antar detta protokoll garanteras att syntetisera x, y, och z egenskaper och implementera arrayFromPosition och magnitud metoder. Även om detta inte säger på vilket sätt de kommer att genomföras, det ger dig möjlighet att definiera ett delat API för en godtycklig uppsättning klasser.

Till exempel, om vi vill ha båda Fartyg och Person För att kunna svara på dessa egenskaper och metoder kan vi berätta för dem att anta protokollet genom att placera det i vinklade fästen efter superklassdeklarationen. Observera också att du, precis som att använda en annan klass, måste importera protokollfilen innan du använder den:

#importera  #import "CoordinateSupport.h" @interface Person: NSObject  @property (copy) NSString * namn; @property (strong) NSMutableSet * vänner; - (tom) sägHello; - (tomrum) sägGodbye; @slutet

Nu, förutom de egenskaper och metoder som definieras i detta gränssnitt, Person klassen är garanterad att svara på API definierat av CoordinateSupport. Xcode kommer att varna dig att Person Implementeringen är ofullständig tills du syntetiserar x, y, och z, och implementera arrayFromPosition och magnitud:

Ofullständig implementeringsvarning för Person

På samma sätt kan en kategori anta ett protokoll genom att lägga till det efter kategorin. Till exempel, berätta för Person klass för att anta CoordinateSupport protokoll i relationer kategori, skulle du använda följande rad:

@interface Person (Relationer) 

Och om din klass måste anta mer än ett protokoll kan du skilja dem med kommatecken:

@interface Person: NSObject 

Fördelar med protokoll

Utan protokoll skulle vi ha två alternativ för att säkerställa båda Fartyg och Person genomförde detta delade API:

  1. Re-deklarera exakt samma egenskaper och metoder i båda gränssnitten.
  2. Definiera API: n i en abstrakt superklass och definiera Fartyg och Person som underklasser.

Ingen av dessa alternativ är särskilt tilltalande: den första är överflödig och benägen för mänskliga fel, och den andra är allvarligt begränsande, särskilt om de redan ärver från olika föräldrakurser. Det bör vara klart att protokoll är mycket mer flexibla och återanvändbara, eftersom de skyddar API: n från att vara beroende av en viss klass.

Faktumet att några klassen kan enkelt adoptera ett protokoll gör det möjligt att definiera horisontella relationer ovanpå en befintlig klasshierarki:

Länka orelaterade klasser med ett protokoll

På grund av protokollens flexibla karaktär gör de olika iOS-ramarna en bra användning av dem. Till exempel konfigureras användargränssnittskontroller ofta med hjälp av delegationsdesignmönstret, där ett delegatobjekt ansvarar för att reagera på användaråtgärder. I stället för att inkapslera en delegats ansvar i en abstrakt klass och tvinga delegater att delklassificera den, definierar iOS det nödvändiga API för delegaten i ett protokoll. På så sätt är det otroligt enkelt för några föremål för att fungera som delegatobjektet. Vi kommer att undersöka det här i mycket mer detalj i andra halvan av denna serie, IOS Succinctly.


Protokoll som pseudotyper

Protokoll kan användas som psuedo-datatyper. I stället för att se till att en variabel är en förekomst av en klass, använder ett protokoll som ett typkontrollverktyg att variabeln alltid överensstämmer med ett godtyckligt API. Till exempel följande person variabel garanteras att implementera CoordinateSupport API.

Person  * person = [[Personallokering] init];

Det är dock ofta mer användbart att tillämpa protokollupptagning när den används tillsammans med id data typ. Detta låter dig anta vissa metoder och egenskaper samtidigt som objektet är helt bortse från objektets klass.

Och naturligtvis kan samma syntax användas med en metodparameter. Följande kod lägger till en ny getDistanceFromObject: Metod till API vars parameter krävs för att överensstämma med CoordinateSupport protokoll:

// CoordinateSupport.h #import  @protocol CoordinateSupport  @property double x; @property double y; @property double z; - (NSArray *) arrayFromPosition; - (dubbel) magnitude; - (dubbel) getDistanceFromObject: (id )objektet; @slutet

Observera att det är helt möjligt att använda ett protokoll i samma fil som den definieras.

Kontroll av dynamisk överensstämmelse

Förutom den statiska typkontroll som beskrivs i det sista avsnittet kan du också använda conformsToProtocol: Metod definierad av NSObject protokoll för att dynamiskt kontrollera om ett objekt överensstämmer med ett protokoll eller inte. Detta är användbart för att förhindra fel när du arbetar med dynamiska objekt (objekt som skrivs som id).

Följande exempel förutsätter Person klass adopterar CoordinateSupport protokoll, medan Fartyg klassen gör det inte. Det använder ett dynamiskt typat objekt som heter mysteryObject att lagra en förekomst av Person,och använder sedan conformsToProtocol: för att kontrollera om det har koordinatstöd. Om det gör det är det säkert att använda x, y, och z egenskaper, liksom de andra metoder som deklarerats i CoordinateSupport protokoll:

// main.m #import  #import "Person.h" #import "Ship.h" int main (int argc, const char * argv []) @ autoreleasepool id mysteryObject = [[Personallokering] init]; [mysteryObject setX: 10.0]; [mysteryObject setY: 0.0]; [mysteryObject setZ: 7.5]; // Uncomment nästa rad för att se "annars" delen av villkorat. // mysteryObject = [[Ship alloc] init]; om ([mysteryObject conformsToProtocol: @protocol (CoordinateSupport)]) NSLog (@ "Ok att anta koordinatsupport."); NSLog (@ "Objektet ligger på (% 0.2f,% 0.2f,% 0.2f)", [mysteryObject x], [mysteryObject y], [mysteryObject z]);  else NSLog (@ "Fel: Ej ​​säkert att anta koordinatsupport."); NSLog (@ "Jag har ingen aning om var objektet är ...");  returnera 0; 

Om du inte kommenterar linjen som omfördelas mysteryObject till a Fartyg Exempel på conformsToProtocol: Metoden kommer att återvända NEJ, och du kommer inte att kunna använda det API som definieras av CoordinateSupport. Om du inte är säker på vilken typ av objekt en variabel kommer att hålla, är denna typ av dynamisk protokollkontroll viktigt för att förhindra att programmet kraschar när du försöker ringa en metod som inte existerar.

Observera också det nya @protokoll() direktiv. Det fungerar så mycket @väljare(), utom i stället för ett metodnamn, tar det ett protokollnamn. Den returnerar a Protokoll objekt som kan vidarebefordras till conformsToProtocol:, bland annat inbyggda metoder. Protokollhuvudfilen gör det inte måste importeras till @protokoll() att jobba.


Forward-Declaration Protocols

Om du slutar arbeta med många protokoll kommer du så småningom att gå in i en situation där två protokoll är beroende av varandra. Detta cirkulära förhållande utgör ett problem för kompilatorn, eftersom det inte framgångsrikt kan importera någon av dem utan den andra. Låt oss till exempel säga att vi försökte abstrahera vissa GPS-funktionaliteter till en GPSSupport protokoll, men vill kunna konvertera mellan "normala" koordinater för vår befintliga CoordinateSupport och koordinaterna som används av GPSSupport. De GPSSupport protokollet är ganska enkelt:

#importera  #import "CoordinateSupport.h" @protocol GPSSupport  - (Void) copyCoordinatesFromObject: (id )objektet; @slutet

Detta ställer inga problem, det vill säga tills vi behöver referera till GPSSupport protokoll från CoordinateSupport.h:

#importera  #import "GPSSupport.h" @protocol CoordinateSupport  @property double x; @property double y; @property double z; - (NSArray *) arrayFromPosition; - (dubbel) magnitude; - (dubbel) getDistanceFromObject: (id )objektet; - (void) copyGPSCoordinatesFromObject: (id )objektet; @slutet

Nu den CoordinateSupport.h filen kräver GPSSupport.h filen för att kompilera korrekt och vice versa. Det är en kyckling-eller-ägg-typ av problem, och kompilatorn tycker inte om det väldigt mycket:

Kompilatorfel som orsakats av cirkulära protokollreferenser

Att lösa det rekursiva förhållandet är enkelt. Allt du behöver göra är att förklara ett av protokollen istället för att försöka importera det direkt:

#importera  @protocol CoordinateSupport; @protocol GPSSupport  - (Void) copyCoordinatesFromObject: (id )objektet; @slutet

Allt @protocol CoordinateSupport; säger det CoordinateSupport är verkligen ett protokoll och kompilatorn kan anta att det existerar utan att importera det. Notera semikolon i slutet av uttalandet. Detta kan göras i någon av de två protokollen; Poängen är att ta bort den cirkulära referensen. Kompilatorn bryr sig inte om hur du gör det.


Sammanfattning

Protokoll är en oerhört kraftfull funktion av Objective-C. De låter dig fånga relationer mellan godtyckliga klasser när det inte är möjligt att ansluta dem med en gemensam föräldraklass. Vi använder flera inbyggda protokoll i IOS Succinctly, så många av kärnfunktionerna i en iPhone eller iPad app definieras som protokoll.

I nästa kapitel introduceras undantag och fel, två mycket viktiga verktyg för att hantera de problem som oundvikligen uppstår när man skriver mål-C-program.

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