Nastal čas na kakao - Metody initWith... a designovaný inicializátor - MujMAC.cz - Apple, Mac OS X, Apple iPod

Odběr fotomagazínu

Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!

 

Zadejte Vaši e-mailovou adresu:

Kamarád fotí rád?

Přihlas ho k odběru fotomagazínu!

 

Zadejte e-mailovou adresu kamaráda:

Soutěž

Sponzorem soutěže je:

IDIF

 

Jaký fotograf/ka získal/a cenu za nejpopulárnější příspěvek v Nikon Photo Contest?

V dnešní soutěži hrajeme o:

Seriály

Více seriálů



Začínáme s

Nastal čas na kakao - Metody initWith... a designovaný inicializátor

12. srpna 2004, 00.00 | V minulém dílu jsme se naučili psát metody init (a dealloc); na chvíli jsme ovšem pozapomněli na metody initWith... s argumenty.

V minulém dílu jsme se naučili psát metody init (a dealloc); na chvíli jsme ovšem pozapomněli na metody initWith... s argumenty.

Metody initWith...

Základní myšlenka je samozřejmě jednoduchá: chceme-li inicializaci nového objektu nějak parametrizovat, prostě použijeme metodu initWith... s patřičnými argumenty, a jejich hodnoty využijeme v její implementaci – dejme tomu nějak takto (s touž třídou Test, na níž jsme si minule ukazovali vzorovou implementaci metody dealloc):

@interface Test:NSObject {
  id vlozenyObjekt;
  void *blokPameti;
  FILE *soubor;
}
...
@implementation Test
...
-initWithBlockSize:(int)size filename:(NSString*)fname {
  if (!(self=[super init])) return nil;
  vlozenyObjekt=[[NSMutableArray alloc] init];
  blokPameti=malloc(size);
  soubor=fopen([fname fileSystemRepresentation],"wt");
  return self;
}
...

Srovnejte tuto implementaci s implementací metody init v minulém dílu!

První problém ovšem nastane ve chvíli, kdy takových metod implementujeme více: je celkem zřejmé, že máme-li výše uvedenou metodu initWithBlockSize:filename:, bylo by hloupé metodu init implementovat tak, jak tomu bylo minule – namísto toho přece můžeme použít mnohem elegantnější a praktičtější variantu

-init {
  return [self initWithBlockSize:1024*1024 filename:@"/tmp/whatever.txt"];
}

Nejenže si tím ušetříme spoustu "ťukání" a tím i snížíme pravděpodobnost chyb; hlavní výhoda je v tom, že takovýto kód se bude mnohem lépe udržovat. Změníme či rozšíříme-li v budoucnosti metodu initWithBlockSize:filename:, metoda init automaticky a okamžitě novinky využije také.

Designovaný inicializátor

Zcela obecně platí, že metod init a initWith... můžeme implementovat libovolné množství; jen jedna jediná z nich by však měla volat inicializátor nadřízené třídy s využitím speciálního příjemce super a vracet self, podobně, jako naše metoda initWithBlockSize:filename:. Je zřejmé, že to bude ta metoda initWith... jež je nejflexibilnější – obvykle tedy ta, jež má nejvíce argumentů. Této metodě budeme říkat designovaný inicializátor.

Všechny ostatní inicializátory by pak prostě měly jen volat designovaný inicializátor a vrátit to, co vrací on – podobně, jako naše metoda init z minulého odstavce.

Mechanismus designovaného inicializátoru ovšem ani zdaleka nekončí jen u toho, že se všechny ostatní inicializátory programují snáze; jeho obrovskou výhodou – zvlášť proti konstruktorům jazyků typu C++ či Java – je to, že umožňuje plně funkční a neomezené dědění inicializátorů.

Jediné, na co je třeba nezapomínat, je to, že vytváříme-li novou třídu, musíme dostát dvěma jednoduchým pravidlům:

  • v jejím designovaném inicializátoru musíme pomocí speciálního příjemce super volat právě designovaný inicializátor nadtřídy;
  • designovaný inicializátor nadtřídy musíme v nové třídě reimplementovat i pokud je její designovaný inicializátor odlišný.

Vše ostatní již pak bude fungovat automaticky. Skutečně – ukažme si příklad třídy Pokus, jež je dědicem třídy Test a přidává ještě jeden vnořený objekt:

@interface Pokus:Test {
  NSString *name;
}
...
@implementation Pokus
...
// designovaný inicializátor této třídy
-initWithBlockSize:(int)size filename:(NSString*)fname name:(NSString*)nm {
  if (!(self=[super initWithBlockSize:size filename:fname])) return nil;
  name=[nm copy];
  return self;
}
// designovaný inicializátor nadtřídy
-initWithBlockSize:(int)size filename:(NSString*)fname {
  return [self initWithBlockSize:size filename:fname name:@"Untitled"];
}
...

Nejenže nyní můžeme využívat oba inicializátory initWith...; stejně dobře můžeme využívat zděděný inicializátor init, a pokud by bývala třída Test měla více (korektně implementovaných) inicializátorů, mohli bychom je bez jakýchkoli obtíží využívat i pro třídu Pokus, aniž bychom je zde museli reimplementovat.

Obrázek ilustruje proč tomu tak je – podívejme se na něj. Modrý ovál representuje objekt, v horní části jsou modře zobrazeny třídy; šedé šipky jsou zprávy, zelené indikují vyhledávání odpovídajících metod při zaslání zprávě příjemci self, červená pak příjemci super:

  • dostane-li objekt třídy Pokus zprávu init, najde se implementace v nadtřídě (Test) a provede se;
  • byla-li ovšem tato implementace korektní, pak neobsahuje nic jiného, než volání designovaného inicializátoru třídy Test s vhodnými argumenty. Tímto inicializátorem ovšem je initWithBlockSize:filename:, a ten je reimplementován ve třídě Pokus;
  • zavolá se tedy jeho implementace z třídy Pokus. Ta sama volá designovaný inicializátor téže třídy – initWithBlockSize:filename:name:;
  • designovaný inicializátor třídy Pokus volá designovaný inicializátor své nadtřídy – tedy implementaci initWithBlockSize:filename: ze třídy Test.

Na první pohled to snad vypadá trochu složitě, avšak je třeba si uvědomit, že prakticky bez jakéhokoli programování máme pro libovolný počet zděděných inicializátorů vše, co jsme potřebovali: volají se oba designované inicializátory, takže instanční proměnné obou tříd jsou korektně nastaveny.

V praxi přitom nezřídka stačí ještě méně programování, než v našem úmyslně složitějším příkladu, neboť dosti často je designovaným inicializátorem podtřídy táž metoda, jež byla designovaným inicializátorem nadtřídy: v takovém případě je samozřejmě situace jednodušší, neboť stačí implementovat jen jeden jediný inicializátor. Ve třídě Pokus by tomu tak bylo pokud bychom v designovaném inicializátoru neurčovali jméno pomocí argumentu:

// designovaný inicializátor této třídy i nadtřídy
-initWithBlockSize:(int)size filename:(NSString*)fname {
  if (!(self=[super initWithBlockSize:size filename:fname])) return nil;
  name=@"Untitled";
  return self;
}

Shrnutí

Při implementaci inicializátorů je tedy zapotřebí dodržet následující pravidla:

  • právě jeden inicializátor je designovaný; ten volá pomocí super designovaný inicializátor nadtřídy, pak inicializuje vlastní proměnné, a vrátí self (a je vhodné jej jako designovaný explicitně označit v dokumentaci a/nebo v hlavičkovém souboru s interface třídy);
  • všechny ostatní inicializátory jsou implementovány prostě jako použití designovaného inicializátoru (vlastní třídy, tedy s použitím self) s vhodnými argumenty;
  • pokud se designovaný inicializátor implementované třídy liší od designovaného inicializátoru nadtřídy, je třeba jej reimplemenovat podle minulého bodu (neliší-li se, byl ovšem reimplemenován podle bodu prvého).

To je vše – dodržení těchto pravidel zajistí korektní a funkční "děditelnost" všech inicializátorů.

Obsah seriálu (více o seriálu):

Tématické zařazení:

 » Rubriky  » Informace  

 » Rubriky  » Agregator  

 » Rubriky  » Začínáme s  

 

 

 

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: