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:
Začínáme s
Nastal čas na kakao - Vkládání objektů a přesměrování zpráv
29. července 2004, 00.00 | V předminulém dílu jsme si začali povídat o tom, jaké alternativní techniky se v Cocoa často využívají namísto dědičnosti: ukázali jsme si, jak funguje delegace a zběžně jsme se seznámili s mechanismem akce/cíl; minule jsme se seznámili s využitím kategorií pro přidání jednoduchého API k již existující třídě. Dnes toto téma dokončíme ukázkou "pokročilejšího triku" vkládání objektů.
V předminulém dílu jsme si začali povídat o tom, jaké alternativní techniky se v Cocoa často využívají namísto dědičnosti: ukázali jsme si, jak funguje delegace a zběžně jsme se seznámili s mechanismem akce/cíl; minule jsme se seznámili s využitím kategorií pro přidání jednoduchého API k již existující třídě. Dnes toto téma dokončíme ukázkou "pokročilejšího triku" vkládání objektů.
Minule jsme mezi nevýhody využití kategorie zařadili to, že není možné přímo přidávat instanční proměnné, a také to, že nelze změnit standardní chování základní třídy. V takových případech je tedy na místě využít buď dědičnosti, nebo vkládání objektů. Dědění je "stará vesta"; dnes si vysvětlíme, jak funguje vkládání objektů a jaké jsou jeho výhody i nevýhody. Pro lepší ilustraci znovu připomeňme obrázek, uvedený již minule, který schematicky ukazuje využití dědičnosti:
Pro lepší pochopení toho, jak vkládání objektů funguje, se vyplatí si nejprve uvědomit, co se vlastně děje v případě, že objektu Zásobník pošleme nějakou zprávu:
- pokud je odpovídající metoda přímo součástí třídy Zásobník (je-li tedy na naší ilustraci ve skupině "nové služby"), prostě se provede;
- pokud tomu tak není, hledá se odpovídající metoda ve třída Pole.
Vkládání objektů dosahuje přesně téhož efektu s využitím trochu odlišného mechanismu: třída Zásobník není dědicem třídy Pole; zato však (mimo jiné) obsahuje vložený objekt třídy Pole. Sama třída pak je implementována tak, aby se zprávy, jimž sama "nerozumí", přesměrovaly právě na tento vnořený objekt. Graficky bychom si tento případ mohli ilustrovat nějak takto:
Implicitní dědičnost tedy nahrazuje explicitní předávání zpráv; celkový výsledek je ovšem týž.
Samozřejmě, jak si za chvilku ukážeme, vkládání objektů je složitější, než prosté využití dědičnosti: kromě nových služeb – jež jsou ve všech případech implementovány stejným způsobem –, totiž musíme navíc explicitně implementovat předávání zpráv. Výměnou za to ovšem získáme značnou flexibilitu:
- vložený objekt nemusí být vytvořen hned při tvorbě hlavního objektu; lze jej vytvořit "on-demand" až ve chvíli, kdy je poprvé skutečně zapotřebí. V některých případech to může znamenat skutečně zásadní rozdíl: pokud se instancí základních objektů vytváří mnoho, pokud jsou vložené objekty velké a používají se spíše výjimečně, může tato technika mnohonásobně snížit spotřebu paměti a zrychlit aplikaci;
- vložený objekt nemusí být po celou dobu existence základního objektu týž: podle potřeby může základní objekt kdykoli vložený objekt nahradit jinou instancí; v komplexnějších případech dokonce i instancí jiné třídy. Podobně může být vložených objektů více, a základní objekt se může podle podmínek a potřeby dynamicky rozhodnout, kterému z nich zprávu předá. Tak můžeme efektivně implementovat de-facto vícenásobnou dědičnost – aniž bychom narazili na kteroukoli z nevýhod, jež jsou s ní spjaty v jazycích typu C++, jež ji nabízejí přímo;
- díky tomu, že předávání zpráv mezi základním a vloženým objektem je "jejich interní věc", skrytá v rámci zapouzdření uvnitř API základního objektu a nikterak neovlivňující zbytek aplikace, můžeme pro něj použít různé nestandardní metody. Typickým využitím této možnosti jsou distribuované objekty – odkaz na objekt obsahuje identifikaci počítače a procesu, a předávání zpráv je zajištěno prostřednictvím meziprocesové komunikace a/nebo počítačové sítě. Tak můžeme transparentně "přímo" pracovat s objekty, jež jsou fakticky součástí jiného procesu na odlišném počítači...
Kromě toho je další výhodou vkládání objektů, specifickou pro API Cocoa, to, že bez problémů funguje i se sdruženými třídami (class clusters) – "klasická" dědičnost je u těchto speciálních tříd problematická, resp. slouží u nich pro zcela jiný účel.
Rozhraní
Ze stejných důvodů jako minule si ukážeme implementaci jednoduchého zásobníku pomocí vkládání objektů, jakkoli pro tento účel by se v praxi samozřejmě kategorie hodila lépe. Rozhraní je poměrně jednoduché – kromě deklarace API potřebujeme jen jednu instanční proměnnou, jež bude obsahovat odkaz na vložený objekt:
@interface Stack:NSObject { NSMutableArray *array; } +stack; // vytvoří a vrátí nový zásobník -(void)push:object; -pop; // pro prázdný zásobník vyvolá výjimku @end
Samozřejmě, v praxi bychom nejspíš vkládání objektů využili ve složitějších případech, kdy by zřejmě instančních proměnných bylo více.
Implementace
Implementace je tentokrát samozřejmě trochu složitější, a proto si ji ukážeme v několika krocích. Nejprve to nejjednodušší: zásobníková primitiva převedeme na primitiva dynamického pole prakticky stejně, jako tomu bylo u kategorie; jen namísto odkazu self sám na sebe v tomto případě ovšem použijeme odkaz na vložený objekt:
@implementation Stack -(void)push:object { [array addObject:object]; } -pop { id o=[[[array lastObject] retain] autorelease]; [array removeLastObject]; return o; } ...
Celkem jednoduchá je i standardní implementace metody stack a metody init, v níž vytvoříme vložený objekt (správně bychom měli init naprogramovat trošku složitěji, a měli bychom implementovat také metodu dealloc; protentokrát to však obojí přeskočíme, protože "to jsme zatím nebrali" – podrobnosti správné implementace metod init a metodu dealloc si podrobně ukážeme hned v příštím dílu).
... -init { [super init]; array=[[NSMutableArray alloc] init]; return self; } +stack { return [[[self alloc] init] autorelease]; } ...
Pokud – jako v případě našeho zásobníku – požadujeme právě jen nové API (zde reprezentované zprávami stack, push: a pop), jsme hotovi: sice jsme museli napsat trošku víc kódu, než by tomu bylo při použití kategorie nebo podtřídy; zato však se nám neplete dohromady "staré" a "nové" API, a použitá technika funguje stejně dobře se sdruženými i s normálními třídami. Navíc máme výhodu možnosti inicializovat vložený objekt dynamicky až v případě potřeby – prostě bychom zrušili implementaci metody init, a na začátek metod push: a pop bychom přidali řádek
if (!array) array=[[NSMutableArray alloc] init];
V obecném případě však zmíněná výhoda – že se nám neplete "staré" a "nové" API – je ve skutečnosti spíše nevýhodou, neboť to znamená, že nad objekty třídy Stack nemůžeme užívat služeb třídy NSMutableArray, byť bychom třeba chtěli. To je proto, že jsme dosud neimplementovali vlastní přesměrování zpráv – až dosud jsme využívali pouze vkládání objektů; hned to napravíme.
Přesměrování zpráv
Pro přesměrování zpráv musíme využít novou vlastnost Objective C a Cocoa, s níž jsme se dosud neseznámili. Dosavadní popis toho, jak objekty zpracovávají přijaté zprávy, totiž nebyl zcela přesný: před časem jsme si řekli, že
... pokud není součástí implementace metoda pro přijatou zprávu, hledá se metoda pro tuto zprávu v nadtřídě. Není-li ani tam, hledá se v její nadtřídě, a tak pořád dál... dokud nenarazíme na "nejvyšší" třídu, jež již nadtřídu nemá. Teprve nenajde-li se metoda ani tam, je zpráva odmítnuta.
Tak nějak tomu je kupříkladu v Javě; Objective C/Cocoa však nabízí komplexnější a flexibilnější služby. Pokud se totiž nenajde odpovídající metoda v žádné nadtřídě, runtime systém se pokusí odeslat zprávu dynamicky, nezávisle na metodách, jež jsou součástí tříd. Takové dynamické odeslání zprávy probíhá ve dvou krocích:
- nejprve se runtime systém "zeptá" objektu na to, jaké jsou typy argumentů a jaká je návratová hodnota, odpovídající dané zprávě;
- pak runtime systém vytvoří "balíček", reprezentující zprávu i její argumenty (právě proto, aby to bylo korektně možné s libovolnými typy, musel nejprve proběhnout první krok), a ten objektu předá. Objekt s ním může udělat cokoli uzná za vhodné – v našem případě jej prostě předá vloženému objektu, aby jej zpracoval za něj.
Oba kroky jsou realizovány zcela standardním způsobem, pomocí standardizovaných zpráv. To přináší dvě výhody: první, není zapotřebí jazyk komplikovat přidáváním dalších rysů; druhá, dokonce i tyto speciální zprávy lze v případě potřeby dědit či přesměrovávat.
Pro první krok slouží zpráva methodSignatureForSelector:, jejímž argumentem je selektor dané zprávy. Vrácená hodnota je speciální objekt třídy NSMethodSignature; ten určuje podrobně všechny atributy metody, tj. počet a typy jejích argumentů i typ její návratové hodnoty. Pro druhý krok je určena zpráva forwardInvocation:, jejímž argumentem je speciální objekt třídy NSInvocation. Ten právě slouží jako výše zmíněný "balíček" – jeho součástí je jak selektor zprávy, tak i konkrétní hodnoty jejích argumentů, a jeho prostřednictvím se předává i návratová hodnota.
Napoprvé to snad zní trochu složitě, ale konkrétní implementace přesměrování všech neznámých zpráv na vložený objekt je velmi jednoduchá:
... -(NSMethodSignature*)methodSignatureForSelector:(SEL)sel { NSMethodSignature *sig=[super methodSignatureForSelector:sel]; if (!sig) sig=[array methodSignatureForSelector:sel]; return sig; } -(void)forwardInvocation:(NSInvocation*)inv { [inv invokeWithTarget:array]; } @end
Malinko složitější je prvá metoda, a to jen proto, že existují i jiné příležitosti než přesměrování neznámé zprávy, při nichž se může runtime systém "zeptat" objektu na signaturu pro některý selektor. Proto je zapotřebí si nejprve vyžádat standardní signaturu pomocí odeslání zprávy speciálnímu příjemci super – a teprve v případě, že pro daný selektor žádná signatura známá není ("if (!sig)"), vyžádáme si signaturu od vloženého objektu.
Implementace druhé metody forwardInvocation: je už jednodušší, neboť tato metoda se zavolá jen a jenom v případě, že je skutečně zapotřebí přesměrovat některou zprávu. Prostě si tedy vyžádáme od objektu inv, aby zpráva, již reprezentuje, byla zaslána objektu array – to zajistí standardní zpráva invokeWithTarget:, jíž instance třídy NSInvocation rozumějí.
Výhody a nevýhody
Řadu výhod vkládání objektů a přesměrování zpráv jsme si už řekli:
+ vkládání objektů a přesměrování zpráv korektně podporuje sdružené i normální třídy;
+ vložený objekt nemusí být vytvořen hned při tvorbě hlavního objektu; lze jej vytvořit "on-demand" až ve chvíli, kdy je poprvé skutečně zapotřebí;
+ vložený objekt nemusí být po celou dobu existence základního objektu týž: podle potřeby může základní objekt kdykoli vložený objekt nahradit jinou instancí; v komplexnějších případech dokonce i instancí jiné třídy. Podobně může být vložených objektů více, a základní objekt se může podle podmínek a potřeby dynamicky rozhodnout, kterému z nich zprávu předá;
+ díky tomu, že předávání zpráv mezi základním a vloženým objektem je "jejich interní věc", skrytá v rámci zapouzdření uvnitř API základního objektu a nikterak neovlivňující zbytek aplikace, můžeme pro něj použít různé nestandardní metody;
+ při vkládání objektů a přesměrování zpráv lze snadno skrýt API, jež nechceme publikovat (tak, jak by zůstalo skryto kompletní API třídy NSMutableArray, kdybychom přesměrování zpráv neimplementovali; stejně dobře můžeme při přesměrovávání dynamicky volit, které zprávy předáme a které ne);
- oproti využití kategorie ztrácíme výhodu oboustranné kompatibility (dostaneme-li instanci třídy NSMutableArray, nemůžeme ji přímo využívat jako zásobník).
Oproti dědičnosti přesměrování nemá takřka žádnou nevýhodu, nepočítáme-li těch cca osm řádků zdrojového kódu, jež musíme napsat navíc pro implementaci vlastního přesměrování.
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