Az Objective-C programozási nyelv

Objective-C 2.0

Objective-C 2.0 újításai

Az Objective-C 2.0 egy nagyobb módosítása a nyelvnek mely támogatja a Cocoa programozási környezetet. Kicsi, de erőteljes csomagokkal kibővített változata az ANSI C nek, mely támogatja az objektum orientált üzenettovábbítást. Az Objective-C egy rendkívül dinamikus és rugalmas nyelv. Lehetővé teszi a magas szinten végzett gyors munkát, mellyel kifinomult alkalmazások készíthetőek, mégis megőrzi a képességét, hogy a legalacsonyabb szintű rendszer könyvtárakhoz is hozzáférjünk és teljesítmény kritikus kódot készítsünk.

Szemétgyűjtés

Az automatikus memória menedzsment bevezetése az Objective-C futtató környezet legjelentősebb változtatása. Ahogy a legtöbb C alapú nyelv az Objective-C előző verziója is azt kívánta meg a programozóktól, hogy a memóriát manuálisan foglalják és szabadítsák fel. Ennek a folyamatnak a támogatására az Objective-C egy referenciaszámláláson alapuló memória menedzsment rendszert szolgáltatott a retain és release kulcsszavakkal. Ez egyszerűsítette a helyzetet, de még mindig nagy odafigyelést kívánt meg a nyelv használóitól, hogy a megfelelő memória használat biztosítva legyen.

Helyettesítve az Objective-C retain/release –re alapuló referenciaszámlálós rendszerét, az új, finomra hangolt, nagy teljesítményű szemétgyűjtő együttműködik az Objective-C-vel és a Cocoa objektum könyvtárral melyet a MAC-OS részeként szállítanak. Ezzel biztosított a memóriakezelésnek az a könnyebbsége, amely olyan modern programnyelvekre jellemző, mint például a Java. A létező Objective-C 1.0 alkalmazások támogatására a szemétgyűjtés opcionális rendszerként lett megvalósítva. Így ha van egy létező alkalmazásunk, amelyet manuális memóriakezelésre támaszkodva szeretnénk futtatni, akkor nem kell semmit tennünk, ugyanis a szemétgyűjtés eredetileg ki van kapcsolva. A bekapcsoláshoz az aktuális projekt target-jén kell az "Objective-C Garbage Collection"-t "required"-re állítani.

Amint ezt engedélyezzük retain, release autorelease és a többi memóriakezelő metódusnak nincs hatása többé. A copy és a mutableCopy üzenetek természetesen továbbra is lemásolják az objektumok értékeit.

A szemétgyűjtés nem gyűjti a CF stílusú objektumokat, mint a CGImageRef és természetesen a programozónak kell kezelnie a manuálisan allokált memóriát a malloc() és a free() segítségével. Rengeteg kulcsszó van arra, hogy olyan finomabb szemcsézettségű felügyelethez jussunk, mint például a gyenge hivatkozások specifikálása.

Tartsuk észben, hogy ha a szemétgyűjtés be van kapcsolva, akkor a dealloc metódus nem kerül meghívásra, viszont megvalósíthatjuk a finalize-t, mellyel hasonló funkcionalitást érhetünk el, biztosítva azt, hogy az objektum eltakarítson maga után, azonban ez nem minden esetben lehetséges.

A szemétgyűjtés kiküszöböli a "retain köröket" (olyan objektumok melyek megőrzik egymást és sohasem szabadulnak fel).

Szemétgyűjtés teljesítmény szempontjai

Amikor a szemétgyűjtő aktív és begyűjtendő memóriát keres ezt egy önálló szálon futva teszi, így nem befolyásolja észrevehetően az alkalmazásunk teljesítményét, ugyanis az alkalmazás szálait csak olyan rövid ideig blokkolja, míg az feltétlenül szükséges. Mindemellett a szemétgyűjtő alacsonyabb prioritású szálon fut, mint az alkalmazás többi szála, így biztosított annak felfüggesztése, ha új felhasználói esemény érkezik időközben.

A generációs algoritmus használata egy másik módszer, amivel a szemétgyűjtő csökkenti a futásidőben érezhető hatását. Ez azt jelenti, hogy a gyűjtő kihasználja a tényt, hogy egy objektum orientált alkalmazásban a legtöbb objektum természeténél fogva meglehetősen rövid életű. Emellett éppen ezek az objektumok azok, amelyek, a legnagyobb valószínűséggel megtalálhatóak a rendszer cache-ben. A szemétgyűjtő azáltal, hogy a lefoglalt memóriát generációkba sorolja, biztosítja, hogy a legmagasabb prioritással mindig a legújabb generációkkal tud foglalkozni így jelentős mennyiségű memóriát tud gyorsan és könnyedén begyűjteni. A kollektor, annak okán, hogy a hosszú életű, de már nem használt objektumokat is begyűjtse, időnként gyűjtést futtat az alkalmazás teljes objektum gráfjára, de ezt sokkal ritkábban teszi, meg mint az első generációs szemét gyűjtését.

ARC - Automatic Reference Counting

Az ARC egy fordítószintű tulajdonsága az Objective-C nek, ami egyszerűsíti az objuktumaink élettartamának kezelését (memória gazdálkodást), vagyis nem szükséges explicit módon beletenni a forráskódba a retain és release kulcsszavakat. Az ARC nem egy ciklikusan hívódó gyűjtő, vagy szemétgyűjtő, mivel a programozónak továbbra is explicit módon meg kell szabnia az objektumainak élettartamát.

Az ARC be lehet kapcsolni explicit a –fobjc-arc fordító kapcsolóval, de hasonlóképpen ki is lehet kapcsolni azt amennyiben nincs rá szükségünk a –fno-objc-arc kapcsoló segítségével.

Amennyiben az ARC be van kapcsolva, a __has_feature(objc_arc) –et a fordító 1 el fogja helyettesíteni a az előfordítás során.

ARC bekapcsolása XCode 4.2 és magasabb verziószám esetében.

Referencia számlált memória

Amikor alloc-álunk egy objektumot az alloc/init segítségével, visszatér az objektum 1-es retainCount-al, ami egyben azt is jelenti, hogy mostantól mi birtokoljuk azt.

NSObject *obj = [[NSObject alloc] init]; // egyéb műveletek [obj release];

Az objektum alloc/init-álása és release-elése között (lemondunk a birtoklásáról), azt tehetünk vele amit csak akarunk, annak tudatában hogy semmiféle képpen sem fog felszabadulni, mialatt használjuk.

Hasonlóan, ha az autorelease pool-hoz adjuk hozzá, akkor az objektum életben marad és csak a jövőben fog valamikor felszabadúlni, mikor már senki sem használja.

-(NSObject*) someMethod { NSObject *obj = [[[NSObject alloc] init] autorelease]; return obj; // az autorelease pool által lesz felszabadítva később }

Referencia számlált memória

Amikor alloc-álunk egy objektumot az alloc/init segítségével, visszatér az objektum 1-es retainCount-al, ami egyben azt is jelenti, hogy mostantól mi birtokoljuk azt.

NSObject *obj = [[NSObject alloc] init]; // egyéb műveletek [obj release];

Az objektum alloc/init-álása és release-elése között (lemondunk a birtoklásáról), azt tehetünk vele amit csak akarunk, annak tudatában hogy semmiféle képpen sem fog felszabadulni, mialatt használjuk.

Hasonlóan, ha az autorelease pool-hoz adjuk hozzá, akkor az objektum életben marad és csak a jövőben fog valamikor felszabadúlni, mikor már senki sem használja.

-(NSObject*) someMethod { NSObject *obj = [[[NSObject alloc] init] autorelease]; return obj; // az autorelease pool által lesz felszabadítva később }

Az ARC működése

Legtöbb esetben nehézkessé válik egy idő múltán a manuális referencia számlálás. Az ARC megoldja ezt elő-fordítási lépésként, azzal hogy hozzáadja a kódunkhoz a szükséges retain/release/autorelease -ek valamelyikét.

Ez nem szemétgyűjtés és a referencia számlált memória kezelés sem tűnt el, csupán autmatizálttá vált.

Bekapcsolt ARC mellett írt kód:

NSObject *obj = [[NSObject alloc] init]; // egyéb műveletek

A megírt kódot, előfordítási lépésként ki fogja egészíti a fordító a következőképpen a következő formára:

NSObject *obj = [[NSObject alloc] init]; // egyéb műveletek [obj release]; // ** ARC által hozzáadva **

A következő diagramm hozzávetőlegesen mutatja, hogy mennyi időt és kódot lehet megspórolni az ARC használata segítségével.

ARC miatt bevezetett új szabályok

A következő szabályok betartása mellett készíthetünk ARC támogatott kódot:

ARC kulcsavai – deklarált property-k

Mint proramozónka sokszor kell az embernek döntéseket hoznia, hogy egy változó vagy konstans lokális, vagy inkább globális legyen, stb… hasonlóan azt is el kell dönteni, hogy az egyik objektum hogyan viszonyuljon egy másik objektumhoz, vagy más szóval milyen tulajdonviszony álljon fent köztük. Ahol korábban:

// Nem ARC kompatibilis kód @property(retain) NSObject *obj;

Az ARC használata mellett a következőt kell tennünk, ahhoz hogy biztosítsuk egy osztály tulajdonviszonyát egy hivatkozott objektummal:

// ARC kompatibilis kód @property(strong) NSObject *obj;

Weak (gyenge) referencia

A weak (gyenge) referencia nem más, mint egy referencia egy másik objetumra, amitől még a hivatkozott objektum bármikor felszabadúlhat, mivel nem mi birtokoljuk azt. Ahol korábban a következőket írhattunk:

// Non-ARC Compliant Declaration @property(assign) NSObject *parentObj;

Ott ehelyett a következőt kell írni, ahhoz hogy ne birtokoljuk a hivatkozott objektumot:

// ARC Compliant Declaration @property(weak) NSObject *parentObj;

ARC kulcsszavak – hétköznapi változók

Hétköznapi változó kulcsszavak, a következők: __strong, __weak, __unsafe_unretained, __autoreleasing.

Általában ezeket a kulcsszavakat nem kell nagyon gyakran használnunk. De ha migrációról beszélünk, vagyis a már meglévő kódunkat szeretnénk ARC kompatibilissé tenni, akkor ezekkel a kucsszavakkal fogunk találkozni legtöbbször, viszont a teljesen új projektek során általában nincsen szükség ezekre, csupán a strong/weak kulcsszavakra a deklarált property-knél.

Property-k

Az Objective-C másik fontos újítása a deklarált property-k támogatása. Az objektum-orientált programozásban a property-k olyan adatot jelentenek, melyek be vannak csomagolva egy objektumba és metódusokon keresztül érhetőek el, nem közvetlenül a példány változóin keresztül. A property-k alkalmazása lehetővé teszi az objektum későbbi módosítását anélkül, hogy más olyan objektumokat is érintene, amelyek használják azt.

Az Objective-C 2.0, hogy jobban támogassa, illetve bátorítsa a property-k használatát, bevezette az új deklarált property szintaxist, amely lehetővé teszi, hogy property-ket adjunk a programunkhoz minimális kódolással. Ahelyett, hogy sok ismétlődő és könnyen hibához vezető boilerplate kódot kellene írnunk, használhatunk, egy tömör deklarációs csomagot mely kifejezi a programozó szándékát. Majd a fordító felhasználja ezeket a deklarációkat, hogy automatikusan legenerálja az accessor metódusokat szükség szerint, hogy megfeleljen az osztály interfészének.

Példa:

@interface Person : NSObject { NSString *location; } @property NSString *location; @end;

Ez a kód mutatja, hogy a Person osztálynak van egy „location” nevezetű property-je amely írható, olvasható. Hogy elkészítsük a property implementálását adjuk a következő kódot az osztály implementációjához:

@implementation Person @synthesize location; @end

Ez a kód utasítja a fordítót, hogy generáljon, vagy szintetizáljon accessor metódusokat. A fordító olyan jól tesztelt gyors accessor metódusokat fog generálni melyek algoritmusa készen áll a több szálas környezetben való futásra. Ebbe beleértendőek a lock-oló változók a setter metódusokban. Később, ha alternatív implementációt kell készíteni a property accessor számára, akkor az osztály egyszerűen ki kell egészíteni a megfelelő kóddal.

A különbség a @property és a @synthesize között elsőre nem biztos, hogy teljesen világos. A property deklarálásáért felel a @property kulcsszó, míg a @synthesize direktíva tulajdonképpen kódot valósít meg az accessorok számára, ha szükséges.

Jegyezzük meg, hogy alapértelmezésként a synthesized accessor-ok atomiak abban az értelemben, hogy a getter biztosan valid értéket add vissza még akkor is, ha több aktív szálunk van. Ennek nincs költsége, ha a szemétgyűjtés be van kapcsolva. A "nonatomic" kulcsszóval kikapcsolhatjuk ezt a fajta viselkedést.

A Property-k az osztály által nyújtott adatdeklarálási általános módjai. Lássuk, hogyan nézett ki egy másik osztály Objective-C 1.x-ben:

@interface Movie : NSObject { NSString* title; NSString* studio; int yearReleased; } + (id)movie; - (NSString*)title; - (void)setTitle:(NSString*)aValue; - (NSString*)studio; - (void)setStudio:(NSString*)aValue; - (int)yearReleased; - (void)setYearReleased:(int)aValue; - (NSString*)summary; @end

És itt található az Objective-C 2.0s kód:

@interface Movie : NSObject { NSString* title; NSString* studio; NSInteger yearReleased; } + (id)movie; @property (copy) NSString* title; @property (copy) NSString* studio; @property (assign) NSInteger yearReleased; @property (readonly) NSString* summary; @end

Vegyük észre, hogy nem minden property. A +movie nevű osztály metódus feladata, hogy generálja az új objektumokat. Nincs szükség arra, hogy ezt property-ként deklaráljuk.

A property deklarálás formátuma:

@property (parameters) type name;

A property paramétere a setter működését határozza meg:

A leggyakrabban használt paraméterek a copy/retain/assign. A programozó választhatja meg, hogyan generálódik, illetve generálódik-e egyáltalán  a setter metódus. A legtöbb Objective-C objektum legjobban a retain-el használható.

Az assign kulcsszó olyan setter-t generál, amelyik közvetlenül a példány változójához rendeli az értéket. Ez leginkább a primitív típusok számára használható, mint pl. az NSInteger és a CGFloat, vagy olyan objektumokhoz melyet a kód írója közvetlenül birtokol, mint például delegate-k. Tartsuk észben, hogy a retain és az assign alapvetően felcserélhetőek, amennyiben a szemétgyűjtés engedélyezve van.

A readonly kulcsszó azt jelenti, hogy nem lesz setter generálva, tehát ezt nem szabad a copy/retain/assign valamelyikével együtt használni

Property-hez való hozzáférés

Ahhoz, hogy egy property-t használni tudjunk a kódban, az Objective-C új pontos szintaxisát használhatjuk, mely ugyanazt a mintát követi mint amit egy struktúra elemének az eléréséhez használunk. Például, hogy a Person osztály location-jét beállítsuk a következő kódot használhatjuk:

Person *bill = [[Person alloc] init]; bill.location = @"Home"

Ezután, hogy elérhessük a property-t az alábbit tegyük:

NSLog(@"Bill's location: %@", bill.location);

Az új pont szintaxis egy szintaktikus üzenet a fordítónak, amely fordítás időben átranszformálódik accessor metódus hívássá. A fenti kód egyenértékű az alábbi metódushívásokkal:

Person *bill = [[Person alloc] init]; [bill setLocation:@"Home"]; NSLog(@"Bill's location: %@", [bill location]);

Az Objective-C 2.0-ban használhatjuk mindkét elérési formát. Nem kötelező a pontot használni a property-khez való hozzáféréshez. A "pontos" szintaxist olyan osztályokon is használhatjuk amelyek nem definiálnak explicit property-ket. Például:

NSString* newString = [textField stringValue]; NSString* newString = textField.stringValue;

Gyors felsorolás (enumeration)

Az OBjective-C 1.x-ben ciklus készítésére a C for és a while utasításai voltak használatosak, illetve az NSEnumerator-ok. Az Objective-C 2.0-ban használhatjuk a gyors enumeráció-t amely a következőképpen néz ki:

NSArray* allMovies = self.movies; for ( Movie* oneMovie in allMovies ) { NSLog ( @"\n", oneMovie.summary ); }

Ez sokkal egyszerűbb és gyorsabb mint a hagyományos for, ezenkívül biztonságosabb is ugyanis ha megpróbáljuk a collection-t enumerálás közben módosítani exception kapunk.

A gyors enumeráció együtt működik minden gyűjteménnyel amely implementálja az NSFastEnumeration protokollt. Ez igaz minden Cocoa gyűjtemény osztályra, úgy mint az NSArray, NSDictionary és NSSet, mivel az NSEnumerator megvalósítja ezt a protokollt.

Néhány tipp:

Amikor propery-ket használunk pont szintakszissal, prefixáljuk a nevet a self-el.

// direct access value = studio; studio = value; // uses accessor methods, sends KVO notifications self.studio = value; value = self.studio

Még ha a szemétgyűjtés be is van kapcsolva, a legtöbb esetben akkor is szükség van az accessor metódusokra, mert a Kulcs – Érték meghatározásnak és a Cocoa binding nek szüksége van arra hogy ezek a meghívódjanak különben nem tudja a változásokat szinkronizálni a objektumok között.

Az NSInteger, NSUInteger és a CGFloat típusok az arhitektúra-biztonságos verziói az int, unsigned int és a float / double típusoknak. Megvédik a programozót az alacsonyszintű, processzor specifikus problémáktól. Ha Leopard-ra fordítunk inkább ezeket használjuk az ANSI C alternatívák helyett. Az NSNumber új metódusokat tartalmaz ezekre a típusokra:

- (id)initWithInteger:(NSInteger)value; - (id)initWithUnsignedInteger:(NSUInteger)value; - (NSInteger)integerValue; - (NSUInteger)unsignedIntegerValue; + (NSNumber *)numberWithInteger:(NSInteger)value; + (NSNumber *)numberWithUnsignedInteger:(NSUInteger)value;

Ha szükség lenne arra, hogy elérjük a C struktúrát egy Objective-C objektumból akkor a szögletes zárójeleket kell használni:

// "bounds" is a C struct. may be too ambiguous for the compiler CGFloat value = self.bounds.origin.y; // works reliably CGFloat value = [self bounds].origin.y;

http://theocacao.com/document.page/510
http://developer.apple.com/leopard/overview/objectivec2.html