Programování v prostředí Cocoa (3)
S Kakaem a metodicky
V minulém pokračování tohoto seriálu jsme se seznámili s pojmy objekt, zpráva a třída. Tentokrát náš krátký přehled základů objektového programování dokončíme.

Jak jsme si minule řekli, protože třída zná všechny informace o objektech, jež reprezentuje, je přirozené, aby sama tyto objekty vytvářela. Je tu však další “nová věc" – neměli bychom podobně, jako jsme přidali do jazyka objekt (a operace nad ním, tj. zasílání zprávy), přidat do jazyka třídu a nějaké operace nad ní? Samozřejmě, bylo by to možné a například C++ to tak dělá. Existuje však daleko elegantnější řešení.
Uvědomme si, že objekty jsme zavedli natolik obecně, že mohou dělat prakticky cokoli – proč by tedy třídy samy nemohly být objekty jako každé jiné? Pro komunikaci s třídami pak můžeme použít naprosto standardní mechanismus zpráv. Jen opět pro lepší čitelnost budeme pro třídy používat namísto typu id typ Class a místo hodnoty nil hodnotu Nil. Znovu ovšem připomeňme, že to děláme jen pro sebe, aby se nám lépe četly zdrojové texty. Překladači to je jedno a vše by fungovalo stejně dobře, i kdybychom používali kdekoli kterýkoli z trojice typů (včetně void*) a hodnot (včetně NULL). Přece jen ale jazyk o něco rozšířit musíme: o prostředky pro tvorbu tříd a pro popis toho, jak budou objekty zpracovávat zprávy.
Poznamenejme, že třídy jsou standardními objekty až na jednu výjimku: samy již nemají žádnou “třídu tříd” čili metatřídu. Bylo by možné ji zavést a některé objektové systémy to skutečně dělají. Praktické výhody jsou však minimální.

Rozhraní, properties, implementace a metody
Popis třídy má dvě jasně oddělené části: rozhraní, které obsahuje informace o tom, jak se s jejími objekty pracuje (a kvůli dědičnosti i něco málo o jejich vnitřní struktuře), a implementaci, jež určuje, jak objekty budou zpracovávat zprávy. Ve zdrojových textech pro jejich popis slouží direktivy @interface, @implementation a @end.
Nejjednodušší rozhraní prostě určí jméno nově vytvářené třídy. Pokud využíváme dědičnosti (což je v praxi téměř vždy), zapíšeme za jméno nové třídy dvojtečku a za ni jméno již existující třídy, od níž chceme novou děděním odvodit (budeme jí říkat nadtřída):

@interface MyClass:NSObject @end

Velice často by se nám hodilo, aby každý objekt třídy obsahoval nějaké vlastní proměnné (properties), jež tak či onak definují jeho obsah: objekt kniha by asi měl proměnné autor, název, vydavatel a podobně. Všechna objektová prostředí proto umožňují v rámci třídy takové proměnné definovat. Je celkem zřejmé, že se obsah těchto proměnných stane součástí toho “něčeho v paměti", co –  jak víme z předešlého dílu –  reprezentuje objekt. Ve zdrojovém textu můžeme takové proměnné definovat ve složených závorkách hned za jménem třídy a nadtřídy:

@interface MyClass2:NSObject
{ // každý objekt třídy MyClass2 bude mít vlastní...
  int i,j; // ...dvě proměnné typu int...
  double d; // ...jednu typu double...
  id o1,o2,o3; // ...a tři (odkazy na) objekty.
}
@end

 (Připomeňme, že id je vlastně ukazatel – např. mezi proměnnou i a o2 je tedy určitý rozdíl, zřejmý zkušeným programátorům v C: číslo i leží skutečně uvnitř objektu třídy MyClass, zatímco objekt o2 je někde venku –  uvnitř objektu třídy MyClass je jen odkaz na něj.)
Pokud měla nějaké vlastní proměnné nadtřída, budou v definované třídě k dispozici také. Jinými slovy, vlastní proměnné kterékoli třídy zahrnují nejen ty, jež jsou deklarovány v jejím rozhraní, ale také všechny deklarované v její nadtřídě, v nadtřídě nadtřídy a tak dále až po “nejvyšší" třídu, která již nadtřídu nemá.
Pečlivý čtenář odstavce, v němž jsem popisoval zprávy, se možná zarazil: zpráva intValue vracela číslo typu int, zpráva doubleValue vracela číslo typu double; tři argumenty zprávy drawCircleWithCentreX:Y:radius:title: byly typu int a čtvrtý char* – jak to má překladač vědět? Snadno: poslední standardní součástí rozhraní je totiž deklarace zpráv a jejich typů. Syntaxe je jednoduchá: před každou zprávu napíšeme znak '-', argumenty označíme identifikátory a před ně i před celou zprávu v závorkách napíšeme typy:

@interface MyClass3:NSObject
{ ... }
-(int)intValue;
-(double)doubleValue;
-(void)drawCircleWithCentreX:(int)x Y:(int)y radius:(int)r title:(char*)tt;
@end

Je důležité mít na paměti, že jde jen o informaci pro překladač! Za běhu pak díky pozdní vazbě může libovolný objekt dostat libovolnou zprávu bez ohledu na to, jestli je zapsaná v jeho rozhraní nebo ne. Můžeme mimochodem používat i zprávy, jež nejsou zapsané v žádném rozhraní: jejich návratové hodnoty i jejich případné argumenty pak budou typu id. Totéž platí pro návratové hodnoty nebo argumenty, u kterých žádný typ v závorce neuvedeme.
Implementace z hlediska programátora vlastně není nic jiného než naprogramování několika metod. Metoda je v zásadě standardní “céčková" funkce – místo hlavičky funkce však použijeme hlavičku, která přesně odpovídá deklaraci zprávy v rozhraní (jen není zakončena středníkem). Překladač pak udělá dvě věci: (a) přeloží kód metody, (b) umístí do třídy informaci, že dostane-li kterýkoli její objekt zprávu odpovídající hlavičce metody, bude vyvolána právě tato metoda. Na rozdíl od deklarací v rozhraní tedy metody v implementaci skutečně popisují chování objektu: dostane-li objekt zprávu, jíž neodpovídá žádná z jeho metod, odmítne ji a dojde k běhové chybě (pro úplnost poznamenejme, že jsou k dispozici prostředky, jak programovat plné dynamické zpracování zpráv, tj. takové, že objekt může zpracovávat například libovolnou zprávu, jejíž jméno začíná na “a” a má sudý počet písmen; prozatím si však takovými věcmi nebudeme komplikovat život).

@implementation MyClass3
-(int)intValue
{
  return 1;
}
-(double)doubleValue
{
  return 1.0;
}
-(char)charValue
{
  return 'a';
}
@end

Povšimněme si, že metody v implementaci neodpovídají přesně zprávám z rozhraní. To, že v implementaci je metod více, je naprosto běžné: odpovídající zprávy z toho či onoho důvodu nejsou součástí rozhraní, ale objekty třídy MyClass3 je přesto dokážou zpracovat. Opačný případ (zpráva uvedená v rozhraní nemá metodu v implementaci) je méně obvyklý, ale také možný.
Uvnitř implementace metod jsou přístupné všechny vlastní proměnné objektu (takže kdybychom například implementovali metodu třídy MyClass2, mohli bychom vracet hodnotu proměnné d příkazem return d;).
Nakonec je třeba říci, že s odmítnutím zprávy a běhovou chybou jsem malinko lhal: pokud totiž není součástí implementace metoda pro přijatou zprávu, hledá se metoda v nadtřídě. Není-li ani tam, hledá se v její nadtřídě, a tak dále, dokud nenarazíme na “nejvyšší" třídu, která již nadtřídu nemá. Teprve nenajde-li se metoda ani tam, je zpráva odmítnuta. To pohodlně a automaticky zajišťuje dědění zpráv: jestliže v implementaci třídy NSObject byla metoda name, můžeme odpovídající zprávu posílat například objektům třídy MyClass3 bez obavy, že by byla odmítnuta.

Metody tříd
Připomeňme si, že třída sama je objektem a sama dokáže přijímat a zpracovávat zprávy. Proto můžeme v rozhraní kromě deklarace zpráv určených pro objekty deklarovat i zprávy určené pro samotnou třídu. Podobně v implementaci můžeme definovat metody, které budou vyvolány v případě, že třída sama dostane zprávu odpovídající hlavičce metody. V obou případech je deklarace i definice stejná jako minule, jen znak '-' na začátku je nahrazen znakem '+':

@interface MyClass4:NSObject
+alloc; // vrátí nový objekt této třídy
+(char*)name; // pro třídu
-(char*)name; // pro objekty
@end
@implementation MyClass4
+alloc { ... }
+(char*)name
{
  return "Třída MyClass4";
}
-(char*)name
{
  return "Objekt MyClass4";
}
@end

Poslední informace, která nám chybí k tomu, abychom mohli začít opravdu programovat, je, jak se dostaneme k “objektu třída" z programu. To je ale prosté: pokud jméno třídy použijeme v hranatých závorkách jako příjemce zprávy, reprezentuje právě požadovaný “objekt třída". Takže malé cvičení pro pozorné čtenáře: je jasné, co vypíše následující funkce, je-li použita po deklaraci a definici třídy MyClass4?

void printout(void) {
  id o=[MyClass4 alloc];
  printf("%s, %s",[MyClass4 name],[o name]);
}

Samozřejmě že metody tříd se dědí analogickým způsobem jako metody objektů: jestliže dostane třída zprávu, pro niž nenajde ve vlastní implementaci žádnou “plusovou" metodu, hledá metodu v implementaci své nadtřídy...

Shrnutí
Ukázali jsme si základní přístup k objektům a principy jejich používání. V rámci příkladů jsme se přitom seznámili s nejdůležitějšími součástmi jazyka Objective C. Ti, kdo mají jeho překladač k dispozici (jako GNU C je k dispozici na libovolné platformě, od Mac OS X přes všechny varianty Unixu až po DOS či Windows), skutečně již mohou začít programovat.
Příště si ukážeme těch několik málo (skutečně málo a poměrně nevýznamných) prvků jazyka Objective C, na něž se zatím nedostalo. Pak se už začneme bavit o skutečných vlastnostech prostředí Cocoa: ukážeme si mechanismus tvorby a zániku objektů a podobně.
Ondřej Čada