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:
Informace
Nastal čas na kakao - Podtřídy, delegáti, vkládání, jak se to rýmuje?
8. července 2004, 00.00 | Inu, třeba: na podtřídy bacha, delegát ten fachá, vkládání je řacha, tak se to rýmuje. Zatímco dědičnost se v Cocoa oproti jiným objektovým prostředím nepoužívá tak často, s výhodou se zde využívají alternativní techniky, mezi něž patří práce s delegáty či vkládání objektů.
Inu, třeba: na podtřídy bacha, delegát ten fachá, vkládání je řacha, tak se to rýmuje. Zatímco dědičnost se v Cocoa oproti jiným objektovým prostředím nepoužívá tak často, s výhodou se zde využívají alternativní techniky, mezi něž patří práce s delegáty či vkládání objektů.
V řadě více či méně objektových prostředí je zvykem dědičnosti využívat takřka na vše možné i nemožné: chceme třeba omezit velikost okna nejvýše na 640x480? Dobrá, vytvoříme si vlastní podtřídu standardní systémové třídy Window, v níž reimplementujeme metodu resize patřičným způsobem, a pro dané okno ji použijeme. Chceme snad tlačítko, jež ukončí aplikaci? Vytvoříme vlastní podtřídu standardní systémové třídy Button, reimplementujeme v ní metodu clicked tak, že aplikaci ukončí, a pro dané tlačítko ji použijeme. Co třeba potřebujeme-li prioritní frontu objektů? Inu, vytvoříme podtřídu standardní třídy Array, a přidáme patřičné služby...
V Cocoa takto obvykle nepostupujeme; to proto, že takovéto přehnané nadužívání dědičnosti není úplně ideální objektový design, a v praxi přináší řadu problémů: nezřídka vede k tomu, že bychom potřebovali vícenásobnou dědičnost (jež je v C++ problematická a jinde není k dispozici vůbec), často je také jeho důsledkem kombinace služeb z logicky různých bloků aplikace v jediném zdrojovém souboru. V důsledku pak omezuje reusabilitu a komplikuje další úpravy kódu.
Dnes se proto blíže podíváme na základní techniky, jež Cocoa nabízí (a prostřednictvím svých standardních tříd také využívá) vedle a namísto tvorby nových podtříd.
Podtřídy
Začít je vhodné tím, že samozřejmě v Cocoa zcela běžně podtřídy vytváříme! Součástí standardních knihoven je dokonce řada tříd, jež jsou přímo určeny k tomu, abychom v konkrétních aplikacích používali jejich konkrétní podtřídy, a samy o sobě téměř nemají smysl: NSView, NSImageRep, NSDocument...
Důležité však je to, že – právě na rozdíl od jiných objektových prostředí – Cocoa často nabízí i jiné prostředky pro dosažení téhož cíle, a velmi často jsou tyto prostředky daleko šikovnější, znamenají menší námahu programátora (a menší pravděpodobnost chyb). Obecně proto v Coca platí následující pravidlo:
Pokud nepracujeme přímo s třídou, jež je navržena pro dědění, měli bychom nejprve zvážit, zda pro vyřešení daného problému nalezneme vhodnou cestu bez vytváření nových tříd. Teprve pokud tomu tak není, měli bychom začít uvažovat o podtřídách; ne dříve.
Podívejme se znovu na příklady z úvodu:
- chceme omezit velikost okna nejvýše na 640x480? Nejjednodušší je použít zcela standardní okno, a přidělit mu delegáta – objekt, jehož se okno "zeptá", má-li změnit velikost daným způsobem;
- chceme tlačítko, jež ukončí aplikaci? Nejjednodušší je použít standardní tlačítko, definovat jeho akci jako zprávu terminate:, a přidělit mu jako cíl objekt aplikace;
- potřebujeme prioritní frontu objektů? Nejjednodušší je použít docela standardní pole, a odpovídající služby doplnit prostřednictvím kategorie;
- případně můžeme vytvořit nový objekt jako dědice třídy NSObject a pole vložit do něj. To je výhodnější v případě, že požadujeme i další atributy – dejme tomu, že by naše prioritní fronta měla mít jméno.
Delegace
Nejjednodušší – a také patrně nejčastěji používaný – vzorec, který dokáže nahradit řadu případů nešikovného dědění, je delegace. Princip je krajně prostý: namísto toho, aby se třída sama o všechno starala prostřednictvím svých metod (jež její dědicové mohou reimplementovat), instance třídy obsahuje odkaz na spolupracující objekt, tzv. delegáta, s nímž nejrůznější akce prostřednictvím odpovídajících zpráv "konsultuje".
Zásadní a klíčovou výhodou tohoto přístupu oproti dědění je to, že můžeme funkčně odlišné bloky kódu skutečně rozdělit do různých tříd. Objektovým designem podle vzorce MVC (Model, View, Controller) se budeme zabývat až později, až se budeme učit jak v Cocoa sestavovat grafické uživatelské rozhraní; i bez větší teorie však snad je zřejmé, že zatímco kód který určuje jak vypadá a jak funguje ovladač pro změnu velikosti logicky patří do objektu "okno", kód který určuje jak se to které konkrétní okno může zmenšovat či zvětšovat tam vůbec nemá co dělat: ten patří do ovladače, který okno řídí, ale rozhodně ne do okna samotného.
Kromě toho využití delegace obvykle i zjednoduší kód aplikace: jen výjimečně delegáta připravujeme jako nový, samostatný objekt; častěji jde o objekt, který slouží zároveň pro více logicky souvisejících věcí – může např. jako delegát řídit okno pro zobrazení seznamu položek, a zároveň jako zdroj dat předávat tyto položky tabulce, jež je uvnitř okna zobrazuje; kromě toho se může ještě starat o aktivaci/deaktivaci tlačítek, jež nad položkami pracují...
Základní mechanismus delegace je prostý, a sami jej ve vlastních třídách můžeme s výhodou využívat: instance prostě má k dispozici proměnnou, obsahující odkaz na delegáta, metody pro přístup k této proměnné, jež se standardně jmenují delegate a setDelegate – a před důležitými operacemi a/nebo po jejich provedení se delegáta zeptá (či jej informuje).
Standardní technika je využít tzv. neformálního protokolu pro deklaraci zpráv, jež jsou delegátovi posílány: nejde o nic jiného, než o rozhraní kategorie pro třídu NSObject. Díky němu překladač zprávy zná a vyhneme se "warningům" při jejich posílání:
// OCJumper.h #import <Foundation/Foundation.h> // deklarace zpráv, posílaných delegátovi: @class OCJumper; @interface NSObject (OCJumperDelegate) -(BOOL)jumperShouldJump:(OCJumper*)jumper; // dotaz, zda má být akce provedena -(void)jumperWillJump:(OCJumper*)jumper; // informace o budoucí akci -(void)jumperDidJump:(OCJumper*)jumper; // informace o provedené akci @end // deklarace vlastní třídy: @interface OCJumper:... { id delegate; ... } -delegate; -(void)setDelegate:del; ... -(void)jump; ... @end
Povšimněme si standardního tvoření jmen zpráv posílaných delegátům: jméno by vždy mělo začínat jménem třídy bez případného prefixu. Následuje should, will či did, jež specifikuje, zda jde o dotaz má-li být akce provedena, o (již nevratnou) informaci o budoucí akci, nebo o informaci o tom, že akce byla ukončena. Pak následuje jméno akce; součástí zprávy samozřejmě musí být argument, který obsahuje konkrétní instanci, jež s delegátem komunikuje (protože jediný delegát může sloužit více různým objektům zároveň). Zprávy obecně samozřejmě mohou mít více argumentů (v tom případě by instance byla hned za jménem třídy: "-(void)jumper:(OCJumper*)jumper will...").
V implementaci stojí za zvláštní zmínku snad jen využití standardní metody respondsToSelector: (zděděné od třídy NSObject) pro ověření, zda delegát té které zprávě skutečně rozumí. To je důležité proto, abychom při implementaci delegáta mohli připravit pouze ty metody, jež potřebujeme, a nemuseli se zdržovat implementací ostatních, jež nás v tu chvíli nezajímají:
// OCJumper.m #import "OCJumper.h" @implementation OCJumper -delegate { return delegate; } -(void)setDelegate:del { delegate=del; // delegát se nikdy "neretainuje"! } ... -(void)performJump { // vlastní akce: není v rozhraní, jde o privátní metodu ... } -(void)jump { // pokud delegát rozumí zprávě jumperShouldJump: a odpoví NO... if ([delegate respondsToSelector:@selector(jumperShouldJump:)] && ![delegate jumperShouldJump:self]) return; // ... neskáčeme // pokud delegát rozumí zprávě jumperWillJump:... if ([delegate respondsToSelector:@selector(jumperWillJump:)]) [delegate jumperWillJump:self]; // ...pošleme mu ji [self performJump]; // provedeme vlastní akci // pokud delegát rozumí zprávě jumperDidJump:... if ([delegate respondsToSelector:@selector(jumperDidJump:)]) [delegate jumperDidJump:self]; // ...pošleme mu ji } ... @end
Důvody proč se delegátu neposílají zprávy retain a autorelease jsou prosté: v praxi je velmi často delegátem objekt, který aplikaci jako celek řídí, a naopak tedy sám podle potřeby vytváří a ruší – jinými slovy tedy "retainuje" a uvolňuje – objekty, jimž slouží jako delegát. Tyto objekty samy tedy delegáta "retainovat" nesmějí, neboť by to vedlo k zacyklení a ani delegát, ani objekt by již nikdy nebyly uvolněny.
Na závěr stojí za to se stručně zmínit o tom, že v moderním Cocoa je klasické využití delegátů, jež jsme si zde popsali, do jisté míry nahrazováno využitím tzv. notifikací, jež je o něco flexibilnější (ale také náročnější). My se notifikacím budeme věnovat později; prozatím je jen vhodné si zapamatovat, že narazíme-li náhodou na problém, při němž bychom potřebovali pro nějakou třídu definovat "více delegátů najednou", je načase podívat se na služby, jež nabízí třída NSNotification ☺
Mechanismus akce/cíl
Ačkoli mechanismus akce/cíl (action/target) se převážně používá u objektů grafického uživatelského rozhraní, stojí za to si jej alespoň ve stručnosti ukázat hned; podrobně se mu budeme věnovat časem, až se soustředíme na GUI.
Jde o určitou alternativu delegace: podobně, jako objekt může udržovat odkaz na delegáta, jejž informuje o významných operacích a/nebo se jej ptá má-li operace provést a jak, může objekt udržovat odkaz také na cíl (target): to je objekt, který bude informován o provedení nějaké zcela zásadní operace. Zcela jednoznačné to bývá právě u objektů grafického uživatelského rozhraní – takto zásadní operací pro tlačítko je jeho stisknutí, pro nabídku výběr některé z jejích položek, pro textové pole ukončení jeho editace a podobně.
Mechanismus akce/cíl je oproti delegaci flexibilnější v tom, že i samotná zpráva, již objekt svému cíli posílá, je proměnná: není tedy napevno určena prostřednictvím neformálního protokolu, jako tomu je u zpráv delegáta; namísto toho instance obsahuje proměnnou typu SEL, a použije standardní metody zděděné od třídy NSObject pro odeslání zprávy – velmi přibližně takto:
// Button.h -- princip ... @interface Button:... { SEL action; id target; ... } ... @end // Button.m -- princip ... @implementation Button ... -(void)performClick { // tlačítko bylo stisknuto [target performSelector:action withObject:target]; } ... @end
Samozřejmě, výhody oproti využití dědičnosti jsou obrovské: nejenže – stejně jako u delegace – nemusíme "míchat" kód pro řízení aplikace s kódem samotného tlačítka; navíc vlastně nemusíme psát vůbec nic "navíc". Pokud je požadovaná služba již někde k dispozici – např. ukončení programu, jež existuje jako hotová metoda terminate: standardní třídy NSApplication – prostě vezmeme tlačítko, a jen nastavíme vhodně jeho target a action (v praxi v Cocoa je na to k dispozici velmi pohodlný grafický prostředek, InterfaceBuilder, takže dokonce ani to nemusíme dělat programově).
Příště se podíváme blíž na pokročilejší techniky – na rozšíření služeb prostřednictvím kategorie, a na vkládání objektů.
Obsah seriálu (více o seriálu):
- Nastal čas na kakao...
- Tak nejdřív kakao ochutnáme...
- Programovací jazyk C: velmi, velmi stručně
- Objective C: to si vysvětlíme podrobněji
- Co jsme si o Objective C ještě neřekli...
- Nastal čas na kakao - Vznik a zánik objektů
- Nastal čas na kakao - Kopírování objektů
- Nastal čas na kakao - Skryté podtřídy
- Nastal čas na kakao - Základní služby objektů
- Nastal čas na kakao - Jak správně psát v Objective C
- Nastal čas na kakao - Jak správně importovat
- Nastal čas na kakao - Podtřídy, delegáti, vkládání, jak se to rýmuje?
- Nastal čas na kakao - Využití kategorií namísto dědičnosti
- Nastal čas na kakao - Vkládání objektů a přesměrování zpráv
- Nastal čas na kakao - Inicializace a rušení objektů
- Nastal čas na kakao - Metody initWith... a designovaný inicializátor
- Nastal čas na kakao - Inicializace: tipy a triky
- Nastal čas na kakao - Accesory: přístup k proměnným instancí
- Nastal čas na kakao - Šedá je teorie, zelený je strom života...
- Nastal čas na kakao - Více o XCode: inspektory
- Nastal čas na kakao - Aplikace RSS2: datový model
- Nastal čas na kakao - Aplikace RSS: implementace datového modelu
- Nastal čas na kakao - Aplikace RSS: parsování XML
- Nastal čas na kakao - Interface Builder a uživatelské rozhraní
- Nastal čas na kakao - Interface Builder: atributy objektů
- Nastal čas na kakao - Interface Builder: atributy objektů
- Nastal čas na kakao - Druhý kontrolér a dokončení aplikace
- Nastal čas na kakao - Drobná vylepšení a zdokonalení...
- Nastal čas na kakao - Ladění
- Nastal čas na kakao - Třídy Foundation Kitu
- Nastal čas na kakao - Třídy Foundation Kitu (2)
- Nastal čas na kakao - Textové řetězce: NS(Mutable)String
- Nastal čas na kakao - Čísla, binární data a další...
- Nastal čas na kakao - Archivace objektů
- Nastal čas na kakao - Trocha magie, aneb distribuované objekty
- Nastal čas na kakao - Málem bychom zapomněli: NSAutoreleasePool
- Nastal čas na kakao - Zpracování výjimek: NSException
- Nastal čas na kakao - NSInvocation a černá magie
- Nastal čas na kakao - Kakao v Tygrovi
- Nastal čas na kakao - Notifikace: nepřímé předávání zpráv
- Nastal čas na kakao - NSUserDefaults
- Nastal čas na kakao - Co nového ve Foundation Kitu
- Nastal čas na kakao – s Intelem, s Intelem, jedeme do...
- Co nového v Xcode
- Začínáme s AppKitem
- Jak MVC v Kakau vypadá doopravdy?
- Jak MVC v Kakau vypadá doopravdy: dokončení
- Přehled tříd AppKitu
- Nastal čas na kakao - Přehled tříd AppKitu 2
- Přehled tříd AppKitu 3: zbývající třídy GUI
- Přehled tříd AppKitu 4: textový systém
- Nastal čas na kakao - Přehled tříd AppKitu 5: hlavně grafika
- Přehled tříd AppKitu 6: dokumentový systém
- Přehled tříd AppKitu 7: dokončení
- Pojmenované vlastnosti objektů
- Pojmenované vlastnosti objektů: implementace
- Pojmenované vlastnosti objektů: relace 1:N
- Pojmenované vlastnosti objektů: řazení jmen a agregační funkce
- Sledování změn objektů
- Sledování změn objektů – ukázka
- Sledování změn objektů – zdrojový kód
- Sledování změn objektů: kód modelu
- Sledování změn objektů: přímý přístup
- Kontroléry a vazby
- Vázání vazeb
- Další vazby s jednoduchým kontrolérem
- Implementace a použití převodu hodnot
- Validace hodnot
- Validace a chyby, a jedna hezká vazba...
- Práce s polem objektů
- Základní vazby NSArrayControlleru
- Převodníky, přepínače, placeholdery
- Mírná vylepšení v mezích zákona
- Objective C 2.0 - novinky z Leoparda
- NSTreeController
- Programování v Cocoa - Pár tipů a triků
- Programování v Cocoa - Základy kreslení
- Kterak nakreslit modrý obdélník...
- Další služby pro kreslení
- Obrázky a písmenka...
- Události a myš
- Lepší práce s myší
- Události klávesnice
- Input Management
- Příkazy a schránka
- Další události
- Táhni a padni
- Byli jsme na tahu; nyní padneme.
- Zvolme si, jak vhodit
- Drobnosti a chybičky
- Speciální případy tahání či házení
- Kterak táhnout něco, co neexistuje?
- Jak na sítě...
- NSURLConnection
- Safari za minutu
- Služby WebKitu
- Kakao v Leopardu
- Druhé Objective C
- Druhé Objective C: různé drobnosti
- Druhé Objective C: kategorie a protokoly
- Druhé Objective C: nový příkaz cyklu
- Druhé Objective C: atributy a accesory
- Druhé Objective C: atributy a accesory
- 64 je dvakrát 32
- Ubicumque dulce est, ibi et acidum invenies...
- Irbis: že prý žádné novinky?
- Blok sem, blok tam, nám už je to všechno jasné...
- Bloky jsou i v AppKitu
- Irbis a Foundation Kit
- Kde jsou má data?
- Kde jsou má data? V NSCache!
- Soubor, jméno, URL, jak se to rýmuje...
- Další podpora NSURL
- Zabíjení!
- A máme tady i...OS!
- Systémové prvky GUI
- Programování pro iOS 1. díl - Rozdíly mezi "i" a "Mac"
- Programování pro iOS - 2. Začínáme programovat
- Programování pro iOS - 3. základní ovladače a propojení GUI s kódem
- Programování pro iOS - 4. Varovná hlášení
- Programování pro iOS - 5. Rámce a jejich řídicí objekty
- Programování pro iOS - 6. Ukládání dat
- Programování pro iOS - 7. Správa paměti a starý restík
- Programování pro iOS - 8. Dokončení aplikace
- Programování pro iOS - 9. Jak dostat aplikaci do iPhone
- Programování pro iOS - 10. Instalace aplikace do cizího iPhone
- Programování pro iOS - 11. Jak dostat aplikaci do libovolného iPhone
- Programování pro iOS - 12. Touching!
- Programování pro iOS - 13. Kreslíme na iPhone
- Programování pro iOS - 14. Udělejme gesto
- Programování pro iOS - 15. Další gesta
- Programování pro iOS - 16. Více prstů, více zábavy
- Programování pro iOS - 17. Podpora standardních gest
- Programování pro iOS - 18. Recognizery v iOS
- Programování pro iOS - 19. Další standardní recognizery
- Programování pro iOS - 20. Co nového v iOSu
- Programování pro iOS - 21. "Multitasking"
- Programování pro iOS - 22. Nulla est honesta avaritia nisi temporis
- Programování pro iOS - 23. Jak se aktivovat, jsme-li v pozadí
- Programování pro iOS - 24. Zbývající drobnosti
- Programování pro iOS - 25. Řídicí objekty rámců
- Programování pro iOS - 26. Jak se dělá UIViewController
- Programování pro iOS - 27. Kde vzít rámce
- Programování pro iOS - 28. Základní služby
- Programování pro iOS - 29. Práce s rámci
- Programování pro iOS - 30. Rotace zařízení
- Programování pro iOS - 31. Správa paměti v rámcích
- Programování pro iOS - 32. Řídicí objekt pro tabulky
- Programování pro iOS - 33. Řídicí objekt pro strom
- Programování pro iOS - 33. Více o UINavigationControlleru
- Programování pro iOS - 35. Ještě jednou UINavigationController
- Programování pro iOS - 36. Po navigátoru taby
- Programování pro iOS - 37. Více o UITabBarControlleru
- Programování pro iOS - 38. Dokončení UITabBarControlleru
- Programování pro iOS - 39. UIPopoverController
- Programování pro iOS - 40. Další triky UIPopoverControlleru
- Programování pro iOS - 41. Zbývající služby UIPopoverControlleru
- Programování pro iOS - 42. UISplitViewController
- Programujeme v
iTunesXcode 4 - Programování pro iOS - 44. Předvolby Xcode 4
- Programování pro iOS - 45. Práce v Xcode 4
- Xcode 4: projekt a cíle
- Xcode 4: práce s cíli
- Xcode 4: Build Settings
- Xcode 4: Build Phases
- Xcode4: Build Phases podruhé
- Xcode 4: Co jsou to Build Rules?
- Xcode4: taje editoru
- Xcode4: automatické doplňování v editoru
- XIBy chyby
- Více o XIBech
- Editor XIBů
- Inspektory pro XIBy
- Vazby mezi objekty v XIBech
- Vazby mezi objekty v kódu
- Paletky Xcode pro XIBy
- Xcode 4: levý sloupec
- Xcode 4: okno Organizer
- Xcode 4: okno Organizer, část druhá
- Xcode 4: co je to Workspace?
- Xcode 4: základy schémat
- Xcode 4: akční schémata