Az Objective-C programozási nyelv

Objektum-orientált programozás



Osztályok

Objective-C-ben úgy definiálunk objektumokat, hogy definiáljuk az osztályukat. Az osztály egy prototípus egy adott objektumra: a keletkező objektumok az osztály adattagjait kapják meg, és az osztályban definiált metódusokat az osztályhoz tartozó objektumok használni tudják. A fordító minden osztályról egyetlen elérhető objektumot készít, egy osztály objektumot, amely tudja, hogy kell az osztály példányait létrehozni. Emiatt ezt az objektumot rendszerint gyártónak hívjuk: factory objectnek. Az osztály objektum az osztály lefordított változata, az objektumok pedig, amiket gyárt, az osztály példányai. A programunkban a "munkát" elvégző objektumok olyan példányok, amiket ez a factory object gyárt le futási időben. Az osztály objektum olyan objektum, ami nem az osztály példánya, nincsenek meg benne az adattagok, és a példánymetódusokat nem hívhatjuk meg rá. Definiálhatunk azonban osztálymetódusokat is, kifejezetten az osztály objektum számára, például írhatunk osztálymetódust, amely megadja az osztály aktuális verzióját. Készíthetünk absztrakt osztályokat, amik nincsenek teljesen definiálva, ám előre megírt hasznos kódokat tartalmazhatnak, így örökléssel sok felesleges munkát megspórolhatunk. A nyelvben nincs olyan megkötés, hogy absztrakt osztályt nem lehet példányosítani, igaz, nem is tudunk osztályt expliciten absztrakttá tenni, mint például C++-ban vagy a C#-ban.

A nyelvben kötelező szétválasztani az interfészt és az implementációt. Az interfész deklarálja az osztály adattagjait, metódusait, és megnevezi az ősosztályát, az implementációban pedig definiáljuk a metódusokat, ezzel tulajdonképpen az osztályt. A két részt tipikusan két fáljba szedjük, előfordulhat azonban, hogy több fáljba esnek szét a részek a kategóriának nevezett nyelvi eszköz miatt. Az interfész deklarálásának szintaktikája (A "ClassName.h" fájlban) :

@interface ClassName : ItsSuperclass { // data member declarations // all datamembers are class level (and protected by default) NSObject * object ; int primitive_type ; // if you want a class level variable use the static keyword static int class_level_var ; } // class and instance method declarations + alloc ; // class level method ( signed with [+] ) - (void) foo ; // instance level method ( signed with [-] ) - (void) bar : (float)fParam ; // instance level method ( signed with [-] ) @end

Megjegyzés: GNUStep fordító alatt kipróbálva az osztályszintű változók ilyen módú deklarációja nem működik, ehelyett a .m állományban javasolt létrehozni a változót ugyanilyen módon (példa a Gyár tervminta World.m fájljában). Ekkor a változó fájl szinten lesz statikus és érdemes az +(void)initialize metódusban inicializálni.

Az osztálynév és az ősosztály megadása után kapcsos zárójelben kell felsorolni az adattagokat. Ezek után kell a kapcsos bezáró zárójel és az @end direktíva között felsorolni az osztály illetve példánymetódusokat. Az osztálymetódusok + jellel, a példánymetódusok - jellel vannak bevezetve, majd kerek zárójelben opcionálisan a visszatérési érték szerepel, majd a metódus neve, aztán opcionálisan kettősponttal elválasztva a paraméterek felsorolása (típus)név formában. Lehetőség van változó számú paraméter átadására a "..." írásával.

Természetesen az ősosztályt ismerni kell, ezért be kell importálni, ha használni akarjuk. Ha az interfész olyan osztályokra hivatkozik, amiket nem tudunk beimportálni, akkor lehetőség van azok elő-deklarációjára :

@class Class1, Class2, ClassN;

Az implementáció megírásakor, ha az külön fájlban van, értelemszerűen be kell importálni az interfész headerfájlját. Az implemetációs részt következőképp definiálhatjuk (ClassName.m fájlban) :

#import "ClassName.h" @implementation ClassName + alloc { //... } - (void) foo } //... } - (void) bar : (float) fParam } //... } } @end

Az osztály adattagjainak láthatóságának szabályozására a @public, @private, @protected és a @package direktívák adnak lehetőséget. A publikus tagok mindenki számára elérhetőek, a privát tagok csak az osztályon belül láthatóak, a védettek osztályon és leszármazottakon belül láthatóak, 64 biten pedig a package láthatóságú adattagok az osztályt implementáló image-en belül publikusként, kívül pedig privátként viselkednek. A default láthatóság a protected.

Objektumok

A nyelvben az objektumok allokálása kötelezően dinamikus. Ez azt jelenti, hogy globálisan vagy veremben (lokális változóként) nem lehet objektumot deklarálni. Természetesen primitív típusokra(pl.: int, float, char) ez a megkötés nem érvényes.

Objektumok deklarálására két lehetőség van.

Self objektum

Minden metódusnak két rejtett paramétere van, a self és a _cmd, vagyis ez

- (id)initWithString:(NSString *)aString;
automatikusan átkonvertálódik erre:
id initWithString(id self, SEL _cmd, NSString *aString);
A self objektum hasonló a C++ programozási nyelvből ismert this-hez. Egy speciális változó, amely egy példánymetódusban az üzenetet fogadó objektumra mutat, amely a metódushívást végrehajtotta. Egy osztályban a hívó osztályt határozza meg. Egy láthatatlan argumentum, amely automatikusan hozzáadódik a példánymetódusokhoz.

Statikus típusmegadás

Az adott objektum osztályának megfelelő mutatót deklarálunk majd az így lefoglalt memóriaterületre inicializáljuk az objektumot. Ezzel már fordítási időben ismertté válik az objektum (statikus) típusa és sok hiba fordítási időben is kiderül, valamint lehetséges kódoptimalizálást végezni.

Dinamikus típusmegadás

Az általános id objektumreferenciatípussal deklaráljuk az objektumra való hivatkozást, ezzel nagyon szabad kezet kapunk a polimorfizmussal, dinamikus kötéssel, öröklődéssel. Elsőre úgy tűnhet hogy az id megfelel más nyelvekben is szereplő ősosztálynak, mint például C#-ban az object. Azonban mivel a nyelv alapvetően dinamikusan típusos, ez a hasonlóság korántsem valós. Hiszen míg C#-ban az object-nek csakis a saját eljárásait hívhatjuk meg addig Objectiv-C-ben az id bármilyen üzenetet képes fogadni. C#-ban egy object típusú változó nagyon korlátos felülettel rendelkezik így általában kockázatos upcast műveletét kell végeznünk a változón hogy a dinamikus típusát jobban kihasználó felületet biztosítsunk számára. Ezzel szemben egy id típusú változó felülete nem korlátos, azaz bármilyen üzenetre potenciálisan képes reagálni.

Az id változó viselkedeését a közkedvelt angolszász modással szokták elmagyarazni, miszerint "ha ugy totyog mint egy kacsa, és úgy hápog mint egy kacsa akkor az egy kacsa!". Azaz az objektum viselkedése dönti el a típusát. A fenti modás mentén szokás ezt a típusosságot duck-typing-nak is nevezni.

//static typing: Rectangle *myRect = [[Square alloc] init]; //dynamic typing: id myRect = [[Rectangle alloc] init]; ... [myRect release];

Az első példában, ha a Square a Rectangle-ből származik, teljes Square objektumot fogunk kapni, nem csak Rectangle-t. Ha viszont olyan példányt akarunk példányosítani, ami nem leszármazott (vagy saját maga), vagy olyan metódusát akarjuk meghívni, ami nincs neki, akkor ez fordítási hibát fog eredményezni. A második példában az id típusú változónk bármilyen objektumra mutathat, kihasználhatjuk a dinamikus kötés minden előnyét, de futási időben keletkeznek csak hibák, amelyeket le kell kezelni.

Minden osztály objektumnak van legalább egy metódusa (mint az alloc), amely helyet foglal a példányoknak, és minden objektumpéldánynak van legalább egy metódusa (mint az init), amely inicializálja és előkészíti a használatra. Minden osztály objektumnak elküld a runtime system egy üzenetet minden más üzenet érkezése előtt, ez az initialize. Ha ezt implementáljuk az osztályunkban, akkor lehetőségünk van az osztály objektumot is előkészíteni:

+ (void)initialize { static BOOL initialized = NO; if (!initialized) { // Perform initialization here. // ... initialized = YES; } }

Konstruktor, destruktor

Objective- C- ben nincs konstruktor metódus. Helyette az init inicializáló metódust használjuk, amely mindig egy id típusú objektummal tér vissza. Az inicializálás mindig a memóriafoglalás után történik, így a példányosítás mindig két lépésből áll: memóriafoglalás és inicializálás. Az init metódus azonban csak abban az esetben megfelelő, ha az inicializáláshoz nincs szükségünk semmilyen bejövő adatra, vagyis paraméterre. Mivel Objective- C- ben nem lehet a függvényeket túlterhelni, ezért a paraméterrel ellátott inicializáló műveleteket nem nevezhetjük init- nek, azonban kötelezően init-el kell kezdődniük. Számtalan osztály rendelkezik beépített inicializáló metódussal, például:

- (id)initWithArray:(NSArray *)array; (fromNSSet)
Inicializáló művelet írásánál érdemes az alábbi példa alapján eljárni:
- (id) init { self = [super init]; //ősosztály inicializálása if (self != nil) //ősosztály sikeres inicializálása esetén { //inicializálás } return self; }
A példában a super kulcsszó az ősosztályra utal, míg a self kulcsszó a metódust hívó objektumra. Azért fontos a super init metódusát meghívni, mert így megbizonyosodhatunk arról, hogy az inicializálás az öröklődési hierarchiában megfelelő sorrendben megtörtént. Amennyiben a super init nil értékkel tér vissza, az init metódusnak is nil-el kell visszatérnie, ellenkező esetben pedig a self objektummal. Az id az általános objektum referencia típus, típusa bármi lehet.

Egy objektum megszűnésekor három metódus hívódik meg automatikusan. Az egyik a –.cxx_destruct metódus, ez nem felülírható a programozó által. A másik két metódus a –finalize és a –dealloc. Amennyiben használjuk a nyelv nyújtotta szemétgyűjtést, foglalkoznunk kell a szemétgyűjtő által nem kezelt erőforrások felszabadításával. Ilyen például a fájl bezárása vagy a malloc() segítségével foglalt memória felszabadítása. Amennyiben egy osztály esetében szükség van hasonló tevékenységre, a –finalize metódust kell deklarálni. Ha nem használjuk a szemétgyűjtőt, akkor a –dealloc metódusban kell elvégeznünk a memória felszabadítását. Mindkét esetben szükséges az üzenet továbbítása az ősosztály felé is, vagyis [ super dealloc] vagy [ super finalize] metódusok hívása. Egy tipikus dealloc metódus a következőképpen néz ki:

- (void)dealloc { // lefoglalt objektumok felszabadítása [super dealloc]; }
A [super dealloc] műveletre azért van szükség, mert tulajdonképpen ez semmisíti meg az aktuális osztálypéldányt, e nélkül a memória nem szabadulna fel.

Osztályobjektumok

Egy osztály definíció különféle információkat tartalmaz, többségük az osztály példányaira vonatkozik:

Ezt az információt a fordító lefordítja, adat struktúrákban rögzíti és elérhetővé tesz a runtime system részére. A fordító egyetlen objektumot készít, az osztályobjektumot, ami az osztályt reprezentálja. Az osztályobjektum hozzáfér az osztály összes információjához, ami főleg arról szól, hogy milyenek az osztály példányai. Képes új példányokat létrehozni az osztálydefinícióban rögzített terv szerint.

Bár az osztályobjektum tartalmaz egy példány prototípust, ő maga nem egy példány. Nincsenek saját példányváltozói, és nem hajthat végre olyan metódusokat amelyek az osztály példányai számára készültek (példány metódusok). Ugyanakkor egy osztály tartalmazhat olyan metódusokat, amelyek kifejezetten az osztály részére készültek (osztály metódusok). Az osztály örökli a hierarchiában fölötte álló osztályok osztálymetódusait,csakúgy ahogy a példányok öröklik a példánymetódusokat.

Változók és osztályobjektumok

Amikor definiálunk egy új osztályt, megadjuk a példányváltozóit. Az osztály minden példánya karbantarthatja a saját másolatát az így deklarált változóknak, azaz minden objektum karbantartja a saját adatait. Ugyanakkor a példányváltozó fogalmának nincsen semmilyen „osztályváltozó” párja. Csak az osztálydefinícióból inicializált, belső adatstruktúrák állnak az osztályok rendelkezésére. Sőt, az osztály objektumnak nincs hozzáférése egyetlen példány példányváltozókhoz sem, nem tudja inicializálni, olvasni vagy módosítani őket.

Hogy egy osztály összes példánya megoszthasson valamilyen adatot, definiálnunk kell valamiféle külső változót. A legegyszerűbb módja ennek, hogy deklarálunk egy változót az osztály implementációs fájlban:

int MCLSGlobalVariable; @implementation MyClass // implementation continues

Egy szofisztikáltabb implementációt is készíthetünk, ha a változót „static”-nak deklaráljuk, és a menedzselésére külön létrehozunk osztálymetódusokat. Ha static-nak deklarálunk egy változót, az limitálja a scope-ját az osztályra, és annak is arra a részére ami a fájlban van definiálva. (Így a példányváltozókkal ellentétben ezeket a változókat az alosztályok se nem öröklik, se módosítani nem tudják.) Ezt a mintát gyakran alkalmazzák egy osztály megosztott példányának létrehozására.

static MyClass *MCLSSharedInstance; @implementation MyClass + (MyClass *)sharedInstance { // check for existence of shared instance // create if necessary return MCLSSharedInstance; } // implementation continues

A statikus változók segítséget nyújtanak abban, hogy a példányok legyártásától eltérő funkcionalitást adjunk az osztályobjektumokhoz, ami így közeledhet ahhoz hogy egy teljes és önálló objektum legyen. Egy osztály objektum felhasználható a példányok koordinálására, hogy töröljön példányokat a legyártott objektumok listájából, vagy egyéb az alkalmazás szempontjából fontos folyamatokat is ütemezhet. Abban az esetben, ha egy bizonyos objektum típusból csak egyetlen példányra van szükség (singleton), annak állapotát reprezentálhatjuk statikus változókkal, amelyeket kizárólag osztálymetódusokból használunk. Ezzel megspóroljuk az objektum allokálásának és inicializálásának költségét.

Osztályobjektumok a gyakorlatban

Bármely osztály, vagy egy osztály bármely példánya felfedheti előttünk az osztály objektumát, ha elküldjük neki a "class" üzenetet. Az így nyert osztály objektumot később felhasználhatjuk típusként (alloc üzenet fogadására), vagy átadhatjuk üzenetben másik objektumnak, amivel a generikus programozáshoz típusparaméterek átadását lehet megvalósítani.

A következő példányosítási módok szintén legálisak:

Class cls = [Rectangle class]; id myRectangle = [cls alloc]; [myRectangle init];
id myRectangle; ... Class cls = [myRectangle class]; id myShape = [cls alloc]; [myShape init];

Öröklődés

Örökléskor a származtatott osztály örökli a bázis osztály összes adattagját és tagfüggvényét. Általában minden osztálynak közvetve vagy közvetlenül őse az NSObject (Amennyiben NEXTStep illetve Cocoa frameworkot használunk). Az osztály objektumok, mivel nincsenek adattagjaik, csak metódusokat örökölnek. Lehetséges örökölt metódusok felüldefiniálása, ekkor ez a metódus hívódik meg, és a későbbi származtatott osztályok, akiknek ez az őse, az új metódust öröklik. A többszörös öröklődést viszont a nyelv nem támogatja, ennek a megkerülésére vezették be a protokollt. A protokoll tulajdonképpen egy adattagok nélküli absztrakt bázisosztály, amely csak metódusokat tartalmaz. Többszörös öröklést tudunk úgy helyettesíteni, hogy egyszeresen öröklünk egy osztályból, és közben az új osztály megfelel egy vagy több protokollnak (interface deklaráláskor kacsacsőrbe téve a protokollokat ):

@protocol Archiving - read: (FILE *) f; - write: (FILE *) f; @end @protocol ReferenceCounting - incrementCount; - decrementCount; - (unsigned) refCount; @end //.................................. @interface Shape : NSObject ...

Ekkor a Shape olyan, mintha az NSObject, Archiving és ReferenceCounting osztályokból öröklődött volna egyszerre, mivel a protokollokban deklarált metódusokat kötelező implementálni.

Polimorfizmus, dinamikus kötés

Az üzenetküldés jelentősége az Objective-C-ben abban áll, hogy az üzenetek és az objektumok nincsenek szorosan összekötve, hanem egy üzenet küldésekor csak futási időben dől el, hogy mi történik. Például egy négyzet és egy kör másképp viselkedik, ha egy üzenettel megkérdezzük a területüket, de az adott objektumról, ami a kezünkben van, nem kell tudni a pontos típusát, hiszen különböző objektumoknak különböző megvalósításai lehetnek ugyanarra a függvényre (polimorfizmus). A metódus neve az üzenetben kiválasztja az implementációt, ezért szokás a metódusneveket az üzenetekben szelektoroknak hívni.

Kategóriák

A kategória segítségével már meglévő osztályokhoz adhatunk hozzá metódusokat - akkor is, ha nincs meg az osztály forrása. Ez egy rendkívül erős eszköz, amellyel öröklés nélkül terjeszthetjük ki az osztályok funkcionalitását. Kategóriák használatával osztályaink implementációját több forrásfájlra is szét tudjuk darabolni. Egy osztályhoz adott metódus az osztály típusnak is része lesz a kategórián belül, a fordító számít arra, hogy ez a metódus létezik az osztály példányaiban. Adattagokat nem vehetünk fel kategóriák segítségével. Ugyanúgy kell metódust hozzáadni osztályhoz, mint egy osztály interfészében/implementációjában, azzal a különbséggel, hogy nem az ősosztályt nevezzük meg, hanem a kategóriát kerek zárójelben. Konvenció, hogy a kategória forrásfájljait "Osztálynév+Kategórianév.*" formában nevezzük el. A kategória deklarálásának és definiálásának formája:

#import "ClassName.h" @interface ClassName ( CategoryName ) // method declarations @end ........................................ #import "ClassName+CategoryName.h" @implementation ClassName ( CategoryName ) // method definitions @end

Természetesen a hozzáadott metódus látja az összes adattagját az osztálynak, még a privátokat is. A kategóriában hozzáadható függvények száma korlátlan, de mindnek más nevet kell adni. A kategóriában adott függvények kiterjeszthetik az osztály f uncionalitását, vagy felülírhatnak örökölt függvényeket. Megvan a lehetőség arra, hogy más kategóriákban definiált függvényeket is felülírjunk, ám ez nem megbízható és nem is ajánlott. Lehetséges az osztályhierarchia gyökerének a kibővítése is: ha az NSObjectet kategorizáljuk, az összes leszármazottban megjelenik az adott függvény, amely néha hasznos tulajdonság, néha veszélyes.

Üzenetküldés

Alapvetően a SmallTalk-ból vette át az Objective-C az üzenetküldés ötletét. Ha egy objektummal csináltatni akarunk valamit, akkor üzenetet küldünk neki, hogy végezze el az adott metódust. Az üzenetküldés szintaktikája:

[receiver message]

A fogadó egy objektum, az üzenet pedig a metódus neve, amit végre kell hajtani. Üzenetküldéskor a runtime system kiválasztja a megfelelő metódust és meg is hívja. Paramétereket is átadhatunk: egy paramétert egyszerűen egy kettőspont után:

[myRect setWidth:20.0];

Több paraméter átadása már nem ilyen egyszerű, mert a paramétereket a nevükkel együtt kell megadni:

p>
[pen draw: circle centerX: 5.0 centerY: 5.0 radius: 10.0];

Visszatérési értéket is kapunk, ha a metódus ad ilyet:

BOOL isFilled; isFilled = [myRect isFilled];

Az üzeneteket egymásba is ágyazhatjuk:

[myRect setPrimaryColor:[otherRect primaryColor]];

Érvényes a nil-nek üzenetet küldeni, ekkor semmi nem történik, de ha a metódusnak, amit hívunk, van visszatérési értéke, akkor előre megszabott esetekben előre megszabott értékeket kapunk, egyébként nem definiált értéket.

Az üzenetküldést kiválthatjuk a "Dot Syntax" alkalmazásával, ami csak szintaktikus cukorka, de mégis hasznos lehet. A nyelv biztosít . operátort, amivel egyszerűbben, üzenetküldés nélkül érjük el a függvényeket:

myInstance.value = @"New value"; NSLog(@"myInstance value: %@", myInstance.value);

ekvivalens ezzel:

[myInstance setValue:@"New value"]; NSLog(@"myInstance value: %@", [myInstance value]);

Szelektorok

Objktive-C-ben a szelektor kifejezésnek két értelme van. Gyakran használjuk egyszerűen egy metódus nevére és szignatúrájára hivatkozva amikor azt a forráskódban üzenetként használjuk. Ugyanakkor azt az egyedi azonosítót is jelentheti, ami a metódus nevét helyettesíti a forráskód lefordítása után. A lefordított szelektorok típusa SEL. Minden azonos nevű metódushoz ugyanaz a szelektor tartozik. Az ilyen szelektort használhatjuk egy objektumon valamilyen akció kiváltására. Ez adja a Cocoa target-action tervezési mintájának alapját.

Metódusok és szelektorok

Hatékonysági szempontok miatt a lefordított kódban nem használunk teljes ASCII neveket szelektorként. Ehelyett a fordító egy táblázatba írja a metódus neveket, majd párosítja a metódus egyedi azonosítójával, ami a metódust futás időben reprezentálja. A runtime system biztosítja, hogy minden azonosító egyedi legyen: Nincs két azonos szelektor, és minden azonos nevű metódushoz ugyanaz a szelektor tartozik.

SEL és @selector

A lefordított szelektorokhoz a speciális SEL típust rendelik, hogy megkülönböztessék őket a többi adattól. Érvényes szelektor értéke sosem lehet 0. Engedd, hogy a rendszer rendelje a metódusokat a SEL azonosítókhoz, az önkényes hozzárendelés hasztalan.

A @selector() direktíva segítségével hivatkozhatunk a lefordított szelektorra a teljes metódusnév helyett. A következő példában a setWidth:height: szelektorát a setWidthHeight változóhoz rendeljük hozzá:

SEL setWidthHeight; setWidthHeight = @selector(setWidth:height:);

A szelektorok SEL változókhoz rendelésének a leghatékonyabb módja a fordítás idejű @selector() direktíva használata. Ugyanakkor, néhány esetben futásidőben kell karakterláncokat szelektorokká konvertálnunk. Ezt megtehetjük az NSSelectorFromString függvénnyel:

setWidthHeight = NSSelectorFromString(aBuffer);

Az ellentétes irányú konverzió szintén lehetséges. Az NSStringFromSelector függvény megadja a szelektor metódusának nevét:

NSString *method; method = NSStringFromSelector(setWidthHeight);
Metódusok és szelektorok

A lefordított szelektorok metódus neveket azonosítanak, és nem metódus implementációkat. Egy osztály display metódusához például ugyanaz a szelektor tartozik, mint a más osztályokban definiált display metódusoknak. Az a polimorfizmus és a dinamikus kötés szempontjából létkérdés, mivel így elküldhetjük ugyanazt az üzenetet a különböző osztályhoz tartozó fogadóknak. Ha egy szelektor – egy metódus mintájú implementációnk lenne, akkor az üzenet nem különbözne egy függvényhívástól.

Az ugyanolyan nevű osztály és példánymetódust ugyanahhoz a szelektorhoz rendeljük. Ugyanakkor, mivel különböző doménhez tartoznak, nem keverhetőek össze. Egy osztály definiálhat egy display metódust, a display példány metódusa mellé.

Metódus visszatérési és paraméter értékek

Az üzenetkezelő rutin a metódus implementációknak csak a szelektorokon keresztül éri el, így az egy szelektorhoz tartozó metódusokat ugyanúgy kezeli. A metódus visszatérési értékét és a paraméterei típusait a szelektorból deríti ki. Emiatt, a statikusan tipizált fogadóknak küldött üzenetek kivételével, a dinamikus kötés megköveteli, hogy az összes azonos nevű metódus implementációnak azonos visszatérési és paraméter típusai legyenek. (Statikusan tipizált fogadók kivételek e szabály alól, hiszen a fordító ismerheti a metódus implementációját az osztály típusból.) Bár az azonos nevű osztály és példány metódusokat azonos szelektor reprezentálja, lehetnek különböző paraméter és visszatérési típusaik.

Protokollok

Objective-C-ben a protokollok az interfészek szerepét töltik be. Egy protokollon belül metódusokat deklarálhatunk, melyeket más osztályoknak implementálnia kell, amennyiben az adott protokollal rendelkezni óhajt. Ezáltal ha csak annyit tudnunk egy osztályról, hogy mely protokollokat implementálja és ismerjük a protokoll metódusait, akkor azokon keresztűl mindenképpen tudjuk használni az osztályt.

A protokoll szintaxisa a következő:

@protocoll myProtocoll - (void) requiredMethod; @optional - (void) optionalMethod; - (void) anotherOptionalMethod; @required - (void) anotherRequiredMethod; @end

Egy protokollban deklarált metódust az őt felhasználó osztálynak alapértelmezetten kötelező implementálnia. Azonban van lehetőségünk opcionálisan megvalsóítható metódusokat is megnevezni. Erre szolgál az @optional direktíva. Amennyiben az opcionális metódusok után kívánunk megadni kötelezően implelementálásra szánt metódusokat, úgy ekkor a @required direktívát használjuk. Protokollt egy osztály a következő szintaxissal adoptálhat:

@interface ClassName : ItsSuperclass < protocol list >

Lehetőségünk van leellenőrizni futásidőben, hogy egy objektum megfelel-e egy protokollnak. Ezt a conformsToProtocol üzenettel tehetjük meg az alábbi módon:

if ( [frac conformsToProtocol: @protocol( NSCopying )] == YES ) { printf( "Fraction conforms to NSCopying\n" ); }

Ezen kívül használhatunk olyan id típust, ami korlátozott egy vagy több protokollra. Ez egyedülálló az Objective C nyelvben, a legtöbb programnyelv ugyanis nem kínál eszközt több protokollnak megfelelő változó létrehozására.

id <Printing, NSCopying> var = frac;

Posing

A Posing hasonlít a kategóriákra egy kis csavarral. Arra ad lehetőséget, hogy származtassunk egy osztályt, majd globálisan helyettesítsük vele az ősét. A helyettesítés után valahányszor az ős osztályt szeretnék használni, a gyermeket használjuk. A korábban létrehozott példányok nem változnak meg, csak az újak. Az alábbi követleményeknek kell teljesülnie a pózoló osztályra:

A pózolás erősebb a kategóriáknál, tehát a pózoló osztály felülírhatja a kategóriaként hozzáadott metódusokat az osztályhoz.

[Subclass poseAsClass: [Superclass class]];

A Posing használatát elavultként jelölték meg Mac OS X v10.5 verziónál és nem elérhető a 64 bites rendszereken.

dealloc, release, retain

Objektumok létrehozásakor, ha nem automatikus objektum felszabadítást választunk, akkor manuálisan kell felszabadítani a memóriát. Erre a feladatra az Objectiv-C egy nagyon kézenfekvő megoldást ad, amelyet az ősosztály az NSObject bíztosít. Gyakorlatilag, ha létrehozunk egy objektumot egy osztályból az alloc szelektorral az alábbi módon:

Teglalap *teglalap = [[Teglalap alloc] init];

akkor azt az alábbi módon fel kell szabadítanunk:

[teglalap release]

Azonban mi történik akkor, ha esetleg ezt az objektumot átadtuk paraméterként egy másik objektumnak, amely a referenciát letárolta és erre a referenciaként továbbra is hivatkozik, sőt ezt az objektumot fel is akarja majd szabadítani? Nyilván ebben az esetben, ha csak szimplán töröljük az eredeti objektumunkat, akkor a máshol használt referencia használata illegális lesz, majd ennek másodszori tőrlése végzetes lehet. Ennek a paradox helyzetnek a feloldására vezették be a retain szelektort, amelyet akkor kell meghívnunk, ha az objektumot több helyen használjuk, illetve menetközben akár több helyen is felszabadítjuk a release-sel. Mielőtt átadnánk paraméternek, hívjuk meg a retain-t:

[teglalap retain]

Gyakorlatilag minden objektum tartalmaz egy referencia számlálót, amelyet a retain növel eggyel, illetve a release csökkent eggyel. Amennyiben ez a referencia számláló eléri a nullát, akkor valóban megtörténik az objektum felszabadítása a release-ben. Általában a referenciák felszabadítását egy adott osztályban a dealloc szelektorban végezzük el. Ez az a szelektor, amely minden objektum felszabadításakor meghívódik automatikusan. Ha ezt felüldefiniáljuk, akkor az ősosztály dealloc-ját vissza kell hívnunk.

- (void) dealloc { //ha van objektum a node-on akkor törüljük azt if (obj!=nil) { [obj release]; } //ha van next elem, akkor elöbb azt is felszabadítjuk if (next!=nil) { [next release]; } [super dealloc]; }

A példaprogramok között (shapes.m) látható példa a fenti manuális memória kezelésre