Zpět Obsah Další

Základy Foundation Kitu


V tomto dílu si řekneme více o základních vlastnostech všech objektů; už se to však nebude týkat libovolného prostředí, ale jen systémů, využívajících Foundation Kit (jako je OpenStep, Cocoa, GNUStep nebo třeba vývojové prostředí XSdk pro Epoc). Řada konkrétních služeb, o kterých zde budeme hovořit, totiž vyžaduje podporu standardních knihoven — např. poloautomatický garbage collector nemůže fungovat bez třídy NSAutoreleasePool, nebo nějakého jejího ekvivalentu. Především se soustředíme na to, jak a kdy objekty zanikají (jejich vznik již známe, jsou podle potřeby vytvářeny třídami).

Řada informací však je i tak relevantní pro libovolné ibjektové prostředí; konkrétní řešení problémů, o nichž se zde zmíníme, však v jiných prostředích mohou být diametrálně odlišná. Např. jazyk Java využívá toho, že v něm neexistuje "ukazatel", a díky tomu může mít plně automatický garbage collector, o jehož funkci se programátor vůbec nemusí starat (platí se za to ovšem zase jinými nevýhodami).

Základní vlastnosti objektů si popíšeme ve třech hlavních odstavcích:

Druhy objektů

Velmi důležitým atributem kteréhokoli objektu je doba jeho existence: kdy objekt vznikne? Kdy zanikne? Je jeho vznik — nebo zánik — vedlejším efektem některé jiné akce programu, nebo si jej musí programátor vyžádat? Z tohoto hlediska můžeme objekty rozdělit v zásadě do čtyř skupin. První tři skupiny dobře známe: odpovídají trvání proměnných ve standardních programovacích jazycích. Objektovou novinkou je čtvrtá skupina — objekty, které dokáží 'přežít' i ukončení procesu, který s nimi pracuje. V tomto textu budeme skupiny nazývat následujícími jmény:

Pro rozhodnutí o typech objektů, které vývojové prostředí bude podporovat, existují — stejně jako téměř kdekoli jinde— dvě protichůdné tendence: na jednu stranu je výhodné umožnit práci s co nejširší paletou možných typů, aby programátor měl k dispozici flexibilní aparát služeb; na druhou stranu existence řady různých typů objektů komplikuje programátorské rozhraní a zvyšuje pravděpodobnost chyb.

Cocoa proto vůbec nepodporuje automatické objekty (obsahuje však jednoduchý, ale efektivní poloautomatický garbage collector, který je z programátorského hlediska dokáže plně nahradit). Podpora statických objektů je omezena pouze na třídy (připomeňme, že třídy v Objective C slouží především pro tvorbu nových objektů — musí tedy být samy statické, protože jinak bychom po spuštění programu neměli k dispozici nic, co by objekty dokázalo vytvořit) a na výjimečné speciální případy, usnadňující programování.

Výjimečným speciálním případem jsou statické objekty třídy NSString. Důvodem pro podporu statických objektů této třídy je to, že běžný program obsahuje řadu řetězcových konstant; kdybychom neměli k dispozici statické objekty třídy NSString, museli bychom používat konstanty typu char* a 'řetězcové konstanty' vytvářet dynamicky na jejich základě:

NSString *adresar=[NSString stringWithCString:"/NextDeveloper/Demos"];

I kdybychom si pro tento účel připravili makro, pořád by to bylo poměrně nepohodlné a z hlediska běhu programu zbytečně neefektivní. Speciální podpora pro tvorbu statických instancí NSString naproti tomu podobné konstrukce výrazně zjednoduší:

NSString *adresar=@"/NextDeveloper/Demos";

Pro ostatní třídy podobnou podporu nepotřebujeme, protože jim odpovídající konstanty se používají jen zcela vyjímečně (pokud vůbec).

Automatické objekty

Objective C automatické objekty nepodporuje. Díky existenci garbage collectoru však můžeme s dynamickými objekty pracovat přesně stejně, jako s automatickými:

{ // automatický objekt v C++
  Array cppArray(objekt1,objekt2,objekt3,objekt4,NULL);
  ...
  // objekt zanikne automaticky při opuštění bloku
}

a odpovídající varianta s dynamickým objektem v Objective C:

{
  id anObject=[NSArray arrayWithObjects:
objekt1,objekt2,objekt3,objekt4,nil];
  ...
  // objekt zanikne automaticky jakmile přestane být zapotřebí
}

Na rozdíl od automatického objektu však je zde významný rozdíl mezi "při opuštění bloku" a "až přestane být zapotřebí"; speciálně, v Objective C je naprosto korektní takovýto objekt předat spolupracujícímu objektu, nebo jej vrátit jako návratovou hodnotu:

{
  id anObject=[NSArray arrayWithObjects:.........];
  ...
  [jinyObjekt budePracovatS:anObject];
  return anObject;
}

S automatickým objektem by něco podobného bylo možné jen za cenu předávání hodnotou, a to je samozřejmě u objektů, jež mohou obsahovat rozsáhlá data, obecně nežádoucí. V Objective C to funguje korektně i při předávání referencí (připomeňme, že typ id sám je reference, protože není ničím jiným, než ukazatelem na objekt, void*).

Dynamické objekty a garbage collector

Dynamické objekty již vlastně známe: objekt je vytvořen na základě explicitního požadavku nějakým jiným objektem (obvykle, ale ne nutně, třídou) . Každý řádek v následujícím příkladu vytvoří nový objekt:

id image=[NSImage imageNamed:@"....."]; // objekt vytvořen třídou
NSString *desc=[image description]; // objekt vytvořen jiným objektem
NSString *descLwr=[desc lowercaseString]; // objekt vytvořen jiným objektem

Součástí API Cocoa je poloautomatický garbage collector. Díky jeho existenci se na dynamický objekt standardně musíme dívat spíše jako na automatický (jak jsme si ostatně ukázali v minulém odstavci): objekt bude jistě existovat po celou dobu zpracování aktuální metody, ale potom jej garbage collector může odstranit.

Konkrétně to tedy znamená, že nebudeme-li žádný z objektů, vytvořených v posledním příkladu, potřebovat později, nemusíme se o jejich uvolnění vůbec starat — garbage collector je uvolní automaticky po ukončení metody, která objekty vytvořila.

Nechceme-li však aby objekt byl odstraněn, musíme garbage collectoru sdělit, že si nad objektem chceme i nadále udržovat kontrolu (proto hovoříme o poloautomatickém garbage collectoru). To uděláme tak, že objektu odešleme zprávu retain — takový objekt pak bude existovat (nejméně) tak dlouho, dokud jej opět neuvolníme. Předpokládejme, že v minulém příkladu si chceme zachovat poslední textový řetězec descLwr  (popis obrázku, uvedený malými písmeny), zatímco zbývající dva objekty byly zapotřebí pouze pro jeho získání a již nás nezajímají:

[descLwr retain];

Po ukončení metody garbage collector uvolní objekty image a desc; objekt descLwr však existuje nadále a můžeme s ním i v budoucnosti volně pracovat. Jakmile zjistíme, že již nebudeme objekt potřebovat, uvolníme jej pomocí zprávy autorelease:

[descLwr autorelease];

a garbage collector jej zruší po ukončení metody, ve které jsme jej uvolnili.

Je vhodné si uvědomit, že pokud jsme napsali "zruší jej po ukončení metody", neznamená to "zruší jej okamžitě po ukončení metody" — objekt může 'přežít' ještě velmi dlouho. Důvod je jednoduchý: dynamické objekty mohou snadno být sdíleny mezi různými moduly nebo různými úseky kódu. S jedním a tím samým objektem descLwr může tedy chtít komunikovat více jiných objektů; každý z nich si může vyžádat udržení objektu zprávou retain. Garbage collector sleduje kolikrát objekt dostal zprávu retain a uvolní jej teprve tehdy, když pro každý retain dostal odpovídající zprávu autorelease.

Poloautomatický garbage collector tohoto typu má řadu výhod. Hlavní z nich je, že se nemusíme explicitně starat o uvolnění sdílených objektů — zcela běžnou situací v objektovém prostředí je, že řada objektů spolupracuje s jedním dalším:

FKSharing

Pokud není k dispozici garbage collector, není jasné, který z objektů 1 až 5 má nakonec uvolnit objekt A. Samozřejmě že ten, který jej přestane potřebovat jako poslední; jak to ale v programu zjistit? Tato situace bývá zdrojem častých chyb (kdy si např. objekt 3 myslí že již nikdo nebude objekt A potřebovat a tak jej uvolní, pak se ale na — již neexistující — objekt A obrátí ještě objekt 4 a program se zhroutí); možnost takových chyb garbage collector definitivně odstraňuje.

Garbage collector tohoto typu má také jednu nevýhodu — ilustruje ji

FKLoop

Zde objekt A poslal zprávu retain objektu B, objekt B objektu C, objekt C objektu D a ten zase objektu B. Jinými slovy, objekt A hodlá ještě komunikovat s objektem B, ten s objektem C, ten s D a ten s B. Je zřejmé, že jakmile pošle objekt A objektu B zprávu autorelease, měly by se správně uvolnit všechny tři objekty B, C a D (protože jsou závislé jen samy na sobě a nikdo již je nebude potřebovat). Garbage collector to ale neví — ten pouze zjistí, že každý z objektů B, C a D dostal vícekrát zprávu retain než autorelease a neuvolní ani jeden z nich.

Musíme si tedy dávat pozor, abychom mezi objekty při odesílání zpráv retain nevytvořili 'cyklus', protože garbage collector takový cyklus neumí rozpoznat a objekty nikdy neuvolní.

Nakonec se seznámíme se zprávou release. Zatímco zpráva autorelease řekne garbage collectoru "tento objekt po ukončení této metody nebudu potřebovat", říká zpráva release "tento objekt od této chvíle nebudu potřebovat". Je tedy její použití o něco málo efektivnější, protože objekt se uvolní ihned a neleží v paměti zbytečně po dobu zpracování metody; při jejím používání si však musíme důkladně rozmyslet víme-li opravdu jistě, že již objekt nebudeme potřebovat.

Podívejme se např. na následující úsek kódu:

...
aFont=[text font];
[currentFont release];
currentFont=[aFont retain];
...

Na první pohled se zdá být vše v pořádku — starý font uvolníme, a místo něj si zapamatujeme aktuální. Přesto toto použití zprávy release může snadno vést k chybě: pokud minulý font je stejný jako dosavadní, uvolní se tento objekt ve chvíli provedení metody release a zpráva retain se již pošle neexistujícímu — právě uvolněnému — objektu! Použijeme-li však zprávu autorelease, je vše v pořádku — garbage collector by objekt uvolnil až po ukončení metody (ale neuvolní jej, protože objekt mezitím dostal zprávu retain).

Úplně nakonec stojí za to se zběžně zmínit o tom, že téměř všechny způsoby vytváření objektů v systému Cocoa vytvoří objekty již "autoreleasované" (tj. takové, které automaticky zaniknou po ukončení metody, nezablokujeme-li si je zprávou retain). Existují pouze tři výjimky: vytvoříme-li nový objekt odesláním standardní zprávy alloc libovolné třídě, nebo odesláním zprávy copy nebo mutableCopy libovolnému jinému objektu, dostaneme objekt který "autoreleasován" není. Ukažme si příklad:

id a1=[NSArray alloc],a2=[NSArray array],a3=[a2 copy],a4=[nejakyObjekt vraciJinyObjekt];
id b1=[NSArray alloc],b2=[NSArray array],b3=[b2 copy],b4=[nejakyObjekt vraciJinyObjekt];
if (...) { // chceme-li, aby garbage collector všechny čtyři objekty a1-4 uvolnil, musíme provést
  [a1 autorelease];
  [a3 autorelease];
} else { // naopak, chceme-li aby garbage collector neuvolnil ani jeden z objektů b1-4, stačí provést
  [b2 retain];
  [b4 retain];
}

To je proto, že zprávu alloc nikdy nepoužíváme samostatně; slouží interně k "výrobě" objektu, který je pak teprve inicializován a "autoreleasován". Ukažme si např. typickou implementaci metody array třídy NSArray, jež byla v minulém příkladu použita:

@implementation NSArray
...
+array
{
  return [[[self alloc] init] autorelease];
}

Zprávy copy a mutableCopy pak slouží spíše jako ekvivalent zprávy retain v případech, kdy nám nestačí objekt "přidržet", ale chceme si zajistit i to, aby jej nikdo jiný nemohl změnit:

...
id myobj=private_copy_needed?[obj copy]:[obj retain];
...
[obj autorelease];

Více si o zprávách copy a mutableCopy řekneme v odstavci, věnovaném měnitelným a něměnným objektům.

Statické objekty

Kromě tříd — které jsou všechny standardně statickými objekty — podporuje Objective C pouze statické objekty třídy NSString. Takový objekt vytvoříme zápisem podobné konstanty, jakou určujeme v plain C textový řetězec; před otevírací uvozovkou však umístíme navíc znak '@':

id aString=@"Text";
// nebo přímo:
if ([aString isEqualToString:@"xyz"]) ...

Objective C automaticky převede ASCII znaky řetězcové konstanty do vnitřního formátu NSStringu, který podporuje Unicode, a vytvoří rovnou při překladu statický objekt třídy NSString s požadovaným obsahem, jenž bude existovat po celou dobu běhu programu.

Za stručnou samostatnou poznámku stojí to, že ačkoli třídy jsou statické, knihovny Cocoa nabízejí prostředky jak vytvářet za běhu programu dynamicky nové třídy — ať již standardním zavedením dynamické knihovny, nebo dokonce přímo programově. Je tedy snadno možné — a v praxi u rozsáhlejších programových systémů velmi často používané — psát kód tohoto typu:

...
class myClass;
if ((myClass=NSClassFromString(@"MyClass"))==nil) {
  // kód pro dynamické zavedení knihovny, obsahující...
  // ...implementaci třídy MyClass
}
// zde již můžeme se třídou volně pracovat
id anObject=[myClass newObject];
...

Ve statických neobjektových jazycích typu C++ samozřejmě na něco podobného není ani pomyšlení; naopak v dynamických objektových systémech je to snadné — ačkoli minulý příklad využíval syntaxe Objective C, např. v Javě snadno implementujeme totéž.

Trvalé objekty

Cocoa standardně umožňuje zapsat libovolný objekt na disk a opět jej z disku obnovit (přesněji řečeno, Cocoa podporuje zápis objektu a jeho opětovné obnovení prostřednictvím libovolného zařízení — disk zajišťuje trvalé objekty, síť předávání objektů mezi počítači a podobně). Opět, je třeba neplést si systém persistentních objektů s jeho nedokonalou náhražkou, již často nabízejí statické jazyky typu C++, a ve které lze obsah objektu zapsat do streamu a naopak nově vytvořený objekt ze streamu inicializovat.

Zásadní rozdíl spočívá v tom, že dynamický systém ukládá na zařízení kompletní informace o objektu, včetně jeho třídy; namísto explicitního vytvoření objektu a načtení jeho obsahu tedy prostě řekneme "dej mi objekt", a načte se to, co na zařízení bylo k dispozici — ať je to cokoli. Je zřejmé, že to nesmírně zjednodušuje API a zvyšuje jeho flexibilitu:

// vytvoření trvalého objektu
id anyObject=......;

[NSArchiver archiveRootObject:anyObject toFile:@"jméno souboru"];
...
// načtení trvalého objektu
id object=[NSUnarchiver unarchiveObjectWithFile:@"jméno souboru"];
NSLog("Získali jsme objekt třídy %@",[object class]);

Vytváříme-li vlastní třídu objektů, stačí velmi jednoduchým způsobem určit jakým způsobem bude nový objekt kódován a dekódován (s podrobnostmi se seznámíme později, až budeme popisovat třídy NSArchiver a NSUnarchiver). Všechny standardní objekty knihoven Cocoa samozřejmě zápis a obnovení podporují.

Díky tomu můžeme libovolný objekt nebo skupinu objektů kdykoli zapsat na disk — objekty se tak stanou trvalými — nebo naopak z disku obnovit.

Měnitelné a neměnné objekty

Základní myšlenkou koncepce měnitelných a neměnných objektů je dosažení vyšší efektivity, aniž by se o to programátor musel vědomě starat. Typickým příkladem je kopírování objektů: v praxi poměrně často potřebujeme vytvořit privátní kopii objektu — jakýsi jeho 'snímek', který uchová momentální stav objektu i v případě, že se původní objekt změní. Představme si například objekt, který reprezentuje hashovací tabulku — takový objekt v Cocoa skutečně existuje a jmenuje se NSDictionary. Základní dvě zprávy, které je schopen zpracovat, jsou

- (void)setObject:(id)anObject forKey:(id)aKey;
- (id)objectForKey:(id)aKey;

První z nich uloží do tabulky dvojici <klíč, hodnota>, druhá vyhledá hodnotu k zadanému klíči (v čase nezávisejícím na počtu hodnot v tabulce). Je zřejmé, že má-li hashovací tabulka být konsistentní, musí interně udržovat ne odkazy na klíče, ale jejich neměnné kopie — kdyby v tabulce byly jen odkazy na klíče, mohl by se objekt reprezentující klíč kdykoli změnit, aniž by se o tom tabulka vůbec dozvěděla; hashovací tabulka by v takovém případě byla samozřejmě nekorektní. Implementace metody setObject:forKey: tedy musí vypadat přibližně takto:

- (void)setObject:(id)anObject forKey:(id)aKey
{
  id myKey=[aKey copy]; // potřebuji vlastní neměnnou kopii
  id myVal=[anObject retain]; // hodnota se může klidně měnit (ale nesmí zaniknout)
  zaradit_do_tabulky(myKey,myVal);
}

Za těchto podmínek bude hashovací tabulka pracovat korektně, ovšem zaplatíme za to zpomalením programu a větší spotřebou paměti: každý klíč vkládaný do tabulky se musí nejprve zkopírovat — to znamená, že potřebujeme dvakrát tolik paměti a navíc program musí kopírovat data objektu. Přitom to v řadě případů není doopravdy zapotřebí: velmi často (v praxi téměř vždy, protože klíče obvykle bývají textové konstanty) se obsah klíčů stejně nebude měnit; hashovací tabulka by si tedy mohla udržovat pouze odkazy na klíče — musela by ale 'vědět', které klíče se ještě mohou měnit a které ne.

Objektové prostředí ale nabízí velmi elegantní řešení: hashovací tabulka samozřejmě nemůže vědět které objekty se budou měnit; mohou to ale vědět tyto objekty samy! Stačí zavést pro každý typ objektů pro který to dává rozumný smysl dvě třídy: třídu neměnných objektů a třídu objektů. které se mohou měnit — např. NSString (neměnné) a NSMutableString (měnitelné). Neměnné objekty pak nemusí nikdy vytvářet kopie — jejich metoda copy může být implementována takto:

@implementation NSString
...
-copy
{
  return [self retain];
}
...
@end

Nyní funguje vše automaticky s nejvyšší možnou efektivitou: vkládáme-li do hashovací tabulky klíč, který se nikdy nebude měnit, hashovací tabulka bude udržovat pouze odkaz — žádná paměť navíc, nic se nekopíruje. Pouze v případě že jako klíč využijeme měnitelný objekt — např. NSMutableString — kopie se vytvoří; v takovém případě se tomu ale stejně nemůžeme vyhnout. Navíc tentýž trik automaticky funguje nejen v hashovací tabulce, ale kdekoli, kde potřebujeme okamžité kopie objektů: připravujeme např. program, který si pro funkci 'undo' musí zapamatovat momentální stav svých datových objektů? Nic jednoduššího — prostě vytvoříme kopie všech objektů reprezentujících data tak, že jim pošleme zprávu copy. Díky koncepci měnitelných a neměnných objektů nemusíme zkoumat, která data se mohou měnit, a která ne — fakticky se zkopírují jen ta, kterých se změny mohou týkat.

Cocoa proto v řadě případů nabízí dvojice tříd NSXXX a NSMutableXXX, kde objekty třídy NSXXX se nemohou měnit, zatímco objekty třídy NSMutableXXX ano (je tomu tak mimochodem i u třídy NSDictionary — metoda setObject:forKey: je tedy samozřejmě k dispozici pouze u objektů třídy NSMutableDictionary). Třída NSMutableXXX je vždy dědicem třídy NSXXX; měnitelné objekty tedy 'umějí' všechno, co neměnné, a navíc jsou schopny změn. Pošleme-li kterémukoli objektu třídy NSXXX zprávu copy, nevytvoří se žádná kopie; namísto toho získáme další odkaz na tentýž (neměnný) objekt. Pošleme-li však zprávu copy objektu třídy NSMutableXXX, dostaneme nový objekt třídy NSXXX, který bude obsahovat neměnnou kopii momentálního stavu původního objektu.

Uvědomme si, že koncepce měnitelných a neměnných objektů zaručuje co nejefektivnější zkopírování i u složených objektů. Jako příklad vezměme objekt třídy NSMutableArray, který reprezentuje pole libovolných dalších objektů, do nějž můžeme přidávat nebo z něj odebírat (odpovídající neměnná třída NSArray reprezentuje pole, jehož obsah nemůžeme měnit).

FKCopy

Obrázek ukazuje příklad objektu třídy NSMutableArray, obsahujícího (odkazy na) jak měnitelné, tak neměnné objekty. Vyžádáme-li si nyní zprávou copy neměnnou kopii momentálního stavu tohoto objektu, musí se vytvořit nový objekt třídy NSArray (protože existující objekt mutableArray je měnitelný) se stejným (a neměnným) obsahem. Nový objekt tedy může se starým sdílet odkazy na neměnné vnořené objekty, ale musí obsahovat vlastní (neměrné) kopie objektů, které byly měnitelné. Výsledek bude vypadat takto:

FKAfterCopy

Čas od času bychom mohli potřebovat 'přece jenom' změnit neměnný objekt. Doslova to samozřejmě není možné — tím bychom celou koncepci měnitelných a neměnných objektů postavili na hlavu; můžeme si však pomocí zprávy mutableCopy vyžádat vytvoření měnitelné kopie objektu. Obsahuje-li původní objekt vnořené objekty, bude jeho měnitelná kopie obsahovat (odkazy na) tytéž objekty, a to i v případě, že tyto objekty samy jsou neměnné (chceme-li např. vytvořit měnitelnou kopii pole, je to proto, abychom do něj mohli přidávat nebo z něj odebírat další objekty; ne proto, abychom mohli měnit objekty v něm obsažené):

FKAfterMutableCopy

Koncepce měnitelných a neměnných objektů je velmi silným a šikovným mechanismem, který kromě výrazného zvýšení efektivity programů dokáže i omezit programátorské chyby: používáme-li neměnný objekt, nemůže se nám omylem stát, že jej některý úsek programu změní (z podobného důvodu byl např. v ANSI C zaveden modifikátor const). Rozdělení měnitelných a neměnných objektů na samostatné třídy NSXXX a NSMutableXXX navíc umožňuje některé takové chyby odchytit již při překladu — pokusíme-li se např. staticky typovanému objektu třídy NSArray poslat zprávu addObject: překladač vydá varování.

Skryté podtřídy

Zatímco koncepce měnitelných a neměnných objektů trochu zkomplikovala programátorské rozhraní Cocoa (namísto jediné třídy např. NSString máme dvě — NSString a NSMutableString) pro zajištění větší efektivity a větší robustnosti, je hlavním účelem koncepce skrytých podtříd programátorské rozhraní bez ztráty efektivity co nejvíce zjednodušit (nebo naopak — při zachování jednoduchého a přehledného API dosáhnout maximální efektivity).

Koncepci skrytých podtříd si opět ukážeme na příkladu. Dejme tomu, že chceme vytvořit třídu, jejíž instance by reprezentovaly čísla (taková třída je součástí Cocoa a jmenuje se NSNumber). Pokud bychom nevyužili koncepce skrytých podtříd, máme v podstatě dvě možnosti:

(1) Vytvoříme třídu NSNumber, která bude sama o sobě schopna pracovat s jakýmkoli typem čísla (char, int, unsigned, long, 64 bitů, float...). To je samozřejmě možné, ale tento přístup má dvě nevýhody: naprogramování takové komplikované třídy je složité, snadno se při něm udělá chyba a složitý zdrojový kód se špatně udržuje. Druhou — a možná závažnější — nevýhodou je, že implementace takové třídy není efektivní, protože musí zahrnovat potřeby všech číselných typů a nemůže být optimalizována pro potřeby jednoho konkrétního typu.

(2) Třída NSNumber sama bude pouze abstraktní nadtřídou, shrnující obecné vlastnosti všech čísel, a skutečnými reprezentanty jednotlivých typů budou její podtřídy — asi takhle:

FKManyClasses

To je lepší, skutečně objektové řešení — každá z podtříd je jednoduchá, snadno udržovatelná a snadno může být také maximálně optimalizována. Nepříjemnou nevýhodou však je velmi komplikované programátorské rozhraní — programátor si musí pamatovat jakési třídy NSCharNumber, NSUnsignedCharNumber..., a musí se sám starat o to, aby se použila potřebná třída. To je nepohodlně, a v objektovém prostředí je to dokonale zbytečné.

Koncepce skrytých podtříd je jednoduchoučký a přitom nesmírně efektivní trik: vlastně se využívá implementace podle bodu (2), ale API programátorům nabízí pouze rozhraní podle bodu (1). Programátor tedy využívá vždy jen a pouze služeb třídy NSNumber a její podtřídy vůbec nezná (jejich konkrétní počet a druhy dokonce vůbec nejsou součástí API, a snadno se mohou měnit mezi jednotlivými versemi systému, bez jakýchkoli záporných důsledků pro kompatibilitu programů). Třída NSNumber sama při vytváření objektu rozhodne, která z jejích (skrytých) podtříd je pro dané číslo optimální a vytvoří odpovídající objekt; i s ním programátor komunikuje jako s objektem třídy NSNumber (což je v naprostém pořádku, protože objekt je dědicem třídy NSNumber). Tuto situaci ilustruje další obrázek:

FKClusters

Vytvoříme-li tedy několik "instancí třídy NSNumber" takto:

NSNumber *aChar = [NSNumber numberWithChar:'a'];
NSNumber *anInt = [NSNumber numberWithInt:1];
NSNumber *aFloat = [NSNumber numberWithFloat:1.0];
NSNumber *aDouble = [NSNumber numberWithDouble:1.0];

může být ve skutečnosti každý z nově vytvořených objektů instancí jiné třídy. Všechny však jsou dědici třídy NSNumber a jako s takovými s nimi můžeme pracovat.

Stojí za to si uvědomit, že toto skvělé řešení je v jazycích typu C++ trochu problematické: jde o špatně navržený systém tvorby objektů — konstrukce "new NSNumber" v C++ prostě nemůže vytvořit objekt jiné třídy, než právě třídy NSNumber; skryté podtřídy zde nelze použít (je možné to do jisté míry dohnat pomocnou statickou metodou, tam však zase nastanou problémy s nemožností tyto metody dědit). Tuto hloupou koncepci z C++ bohužel do značné míry přebral i jinak velmi dobrý objektový jazyk Java.

Cocoa využívá koncepce skrytých podtříd velmi často; právě díky tomu je API Cocoa mnohem jednodušší a přehlednější, než např. C++kové API operačního systému Epoc, přestože služby Epocu nabízejí jen zlomek luxusu a flexibility služeb Cocoa. Typickým příkladem skrytých podtříd jsou prakticky všechny třídy Foundation Kitu, které reprezentují složené objekty (jako NSArray nebo NSDictionary) — ty využívají skrytých podtříd pro volbu optimální implementace z hlediska poměru efektivity a paměťové náročnosti, aniž by se tím musel programátor explicitně zabývat. Programátor nadto samozřejmě může v případě potřeby snadno sám doplnit další skryté podtřídy pro rozšíření služeb celé skupiny tříd.

Shrnutí

Ukázali jsme si základní vlastnosti objektů Cocoa, především z hlediska doby jejich života; již víme, kdy a jak objekty v systému Cocoa zanikají. Navíc jsme se seznámili s některými dalšími paradigmaty, jež zajišťují vysokou efektivitu při udržení jednoduchosti a přehlednosti API.

Příště se zběžně seznámíme s konkrétním prostředím pro programátorskou práci, jež Cocoa nabízí — s aplikací ProjectBuilder.


Zpět Obsah Další

Copyright © Chip, O. Čada 2000-2003