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 - Inicializace: tipy a triky
20. srpna 2004, 00.00 | Než pokročíme k dalšímu tématu (jímž bude správné psaní tzv. accesorů – zběžně je ostatně "naťukneme" již dnes), vyplatí se ještě si ukázat několik triků a správných vzorců při implementaci a užívání inicializátorů. Nakonec si také ukážeme jeden potenciální "podraz", na který si při implementaci inicializátorů musíme – aspoň v těch složitějších případech – trošku dávat pozor.
Než pokročíme k dalšímu tématu (jímž bude správné psaní tzv. accesorů – zběžně je ostatně "naťukneme" již dnes), vyplatí se ještě si ukázat několik triků a správných vzorců při implementaci a užívání inicializátorů. Nakonec si také ukážeme jeden potenciální "podraz", na který si při implementaci inicializátorů musíme – aspoň v těch složitějších případech – trošku dávat pozor.
Alokace na vyžádání
Až dosud jsme si ukazovali "klasický" vzorec, při němž jsou vložené objekty a další zdroje alokovány v metodě init (přesněji, v designovaném inicializátoru, jímž může být init nebo některá z metod initWith...) a uvolněny v metodě dealloc. To je samozřejmě dobře a správně; existují však situace, kdy je šikovnější zdroje alokovat ne hned při vytvoření objektu, ale teprve ve chvíli, kdy jsou skutečně zapotřebí.
Princip je jednoduchý – využijeme prostě toho, že instanční proměnné objektu jsou automaticky nulovány ve chvíli, kdy je objekt standardní zprávou alloc vytvořen, a v designovaném inicializátoru je necháme tak. Na začátku metod, jež s instančními proměnnými pracují, pak prostě ověříme zda patřičná instanční proměnná obsahuje potřebný objekt, a pokud tomu tak není, teprve ji naplníme.
Ukažme si typický příklad objektu, který obsahuje nějaké přiřazení jmen a hodnot, lhostejno jaké. Náš objekt bude pro tento účel obsahovat vložený objekt třídy NSMutableDictionary, k němuž se bude přistupovat pomocí interních metod valueForKey: a setValue:forKey:. Pro lepší ilustraci si nejprve ukážeme tradiční implementaci:
@interface Test:NSObject { NSMutableDictionary *dict; ... } ... @end @implementation Test -init { if (!(self=[super init])) return nil; dict=[[NSMutableDictionary alloc] init]; ... return self; } -(void)dealloc { ... [dict release]; [super dealloc]; } ... -valueForKey:key { return [dict valueForKey:key]; } -(void)setValue:value forKey:key { [dict setValue:value forKey:key]; } ... @end
Většinou se však v takovýchto případech vyplatí nevytvářet vložené objekty typu našeho pomocného slovníku dict hned při inicializaci, ale ponechat to až na chvíli, kdy je to skutečně zapotřebí. Je to tím praktičtější, čím je větší pravděpodobnost, že za určitých okolností se bude s naším objektem pracovat aniž by se vůbec vložený objekt použil, ale také tím praktičtější, čím je vložený objekt náročnější na paměť a/nebo čím je jeho vlastní inicializace komplikovanější.
V praxi tedy velmi často podobný kód píšeme trochu jinak – ukažme si pouze metody, jejichž obsah se změní:
... -init { if (!(self=[super init])) return nil; ... return self; } ... -(void)setValue:value forKey:key { if (!dict) dict=[[NSMutableDictionary alloc] init]; [dict setValue:value forKey:key]; } ...
V extrémním případě, kdy na vyžádání alokujeme všechny zdroje, jež náš objekt používá, se dokonce může stát, že vůbec nebudeme potřebovat vlastní reimplementaci metody init: byla by totiž prázdná, všechna inicializace se provede až ve chvíli, kdy je to skutečně zapotřebí. To je jedna z mála situací, kdy implementujeme pouze jednu z metod init/dealloc a ne obě společně.
Mimochodem, je vhodné si uvědomit, že má-li takovýto kód být korektní, musíme si dávat velký pozor na použití proměnné dict: víceméně platí, že bychom ji neměli nikde používat přímo, jen prostřednictvím pomocných metod valueForKey: (jejíž případné volání dříve, než byla proměnná inicializována, bude korektní díky tomu, že v Objective C můžeme hodnotě nil zaslat libovolnou zprávu a výsledek je opět nil) a setValue:forKey:. Kromě toho je v tomto případě ovšem korektní i použití v metodě dealloc; bylo ale třeba si to explicitně uvědomit.
My se tomuto "odstínění" či "zapouzdření" instančních proměnných pomocí speciálních přístupových metod budeme podrobněji věnovat příště, až si budeme povídat o tzv. accesorech.
Singleton a sdružená třída
Minule jsme se zmínili, že to, že metoda init může vracet jiný objekt, než který odpovídající zprávu přijal, se nejčastěji využívá při implementaci tzv. singletonů (tříd, jež mají jen jedinou sdílenou instanci) a sdružených tříd (jež namísto vlastních instancí při vytváření nových objektů vracejí instance skrytých podtříd).
Aniž bychom se zdržovali příliš podrobným výkladem, vyplatí se ukázat si alespoň rámcový příklad kódu metody init pro oba tyto případy:
@implementation Singleton -init { static Singleton *myself=nil; if (myself) { [self release]; //* return myself; } if (!(self=[super init])) return nil; ... return myself=self; }
Za zmínku stojí řádek, označený hvězdičkou: samozřejmě zde použijeme release (a nikoli autorelease), protože v tomto specifickém případě je skutečně zcela zřejmé, že chceme objekt uvolnit okamžitě.
Je také třeba si uvědomit, že uvolňujeme objekt, který dosud nebyl korektně inicializován (neboť jsme dosud neprovedli "[super init]"). Naprostá většina singletonů je dědicem třídy NSObject, kdy to v praxi příliš nevadí (a naopak je žádoucí "ničím se nezdržovat" a uvolnit objekt co nejdříve); proto uvádíme tuto variantu. Ve zcela obecném případě to však je mírně nekorektní, takže řada programátorů raději celý blok "if (myself)..." přemísťuje až na začátek vlastní inicializace, označené třemi tečkami.
Implementace metody init... ve sdružené třídě je ještě jednodušší – prostě vždy odstraní objekt, který zprávu přijal, a namísto něj vrátí objekt vhodné podtřídy. Metoda init bez argumentů by tedy byla zcela triviální; ukážeme si namísto toho princip implementace metody initWithSize:, jež zvolí vhodnou podtřídu podle požadované velikosti:
@interface Cluster:... @end // sdružená třída, veřejné API @interface Under1000:Cluster ... @end // skrytá podtřída pro malé velikosti @interface Over1000:Cluster ... @end // skrytá podtřída pro velké velikosti ... @implementation Cluster -initWithSize:(int)size { [self release]; if (size<1000) return [[Under1000 alloc] initWithSize:size]; return [[Over1000 alloc] initWithSize:size]; }
Pozor na částečně inicializované objekty!
Mechanismus inicializace, který prostřednictvím metod init... nabízí Objective C a Cocoa, je mnohem flexibilnější, než konstruktory jazyků typu C++ či Java. Nic ovšem není zadarmo: flexibilita inicializátorů, jmenovitě nesmírně praktická možnost jejich volného dědění, může přinést při nevhodném použití problémy. Konkrétně, může se nám stát, že je volána některá z metod objektu dříve, než je objekt plně inicializován.
Vzpomeňme si na strukturu designovaných inicializátorů a jejich dědění z minulého dílu; dejme tomu, že máme třídy Test a jejího dědice Pokus, jež obě mají jediný (designovaný) inicializátor init, a ten že ve třídě Test používá nějakou pomocnou metodu, dejme tomu foobar. Pokud pak metodu foobar třída Pokus reimplementuje, můžeme narazit – podívejme se na konkrétní kód:
@implementation Test -(void)foobar { ... } -init { if (!(self=[super init])) return nil; [self foobar]; return self; } @end @implementation Pokus:Test { NSMutableArray *array; } -(void)foobar { [array addObject:@"FUBAR!"]; //* } -init {
if (!(self=[super init])) return nil; //** array=[[NSMutableArray alloc] init]; return self; } @end
Tento kód nejspíš nebude fungovat tak, jak chceme – protože metoda foobar se zavolá dříve, než proběhne kompletní inicializace třídy Pokus, takže v době jejího volání bude ještě proměnná array obsahovat hodnotu nil, a tudíž příkaz "addObject:" neudělá zhola nic.
Je zřejmé, jak k tomu dojde? Sledujte se mnou:
- nově vytvořený, prázdný objekt třídy Pokus dostane zprávu init;
- odpovídající metoda nejprve volá "[super init]", tedy metodu init třídy Test;
- ta ovšem (po inicializaci nadtřídy) hned pošle sama sobě zprávu foobar;
- jsme tedy v metodě foobar třídy Pokus na řádku označeném hvězdičkou; v inicializaci jsme se však dosud nedostali dále, než na řádek označený dvěma hvězdičkami – speciálně, proměnná array dosud nebyla naplněna.
Samozřejmě, to je jen další důvod používat alokaci na vyžádání, s níž jsme se seznámili na začátku dnešního článku: kdyby bývala metoda foobar začínala příkazem "if (!array) array=[[NSMutableArray alloc] init]", vše by bylo v nejlepším pořádku.
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