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 - Aplikace RSS: parsování XML
21. října 2004, 00.00 | Předminule jsme probrali strukturu datového modelu naší aplikace a rozhraní třídy RSS; v minulém dílu jsme si ukázali její implementaci – inicializaci, standardní metodu dealloc, a accesory. Zbývá nám tedy ještě ukázat si, jak je implementováno parsování XML, a pak se už budeme moci pustit do tvorby grafického uživatelského rozhraní.
Předminule jsme probrali strukturu datového modelu naší aplikace a rozhraní třídy RSS; v minulém dílu jsme si ukázali její implementaci – inicializaci, standardní metodu dealloc, a accesory. Zbývá nám tedy ještě ukázat si, jak je implementováno parsování XML, a pak se už budeme moci pustit do tvorby grafického uživatelského rozhraní. (Znovu se možná vyplatí zdůraznit, že toto pořadí prací jsme si volně vybrali – Cocoa umožňuje to dělat třeba naopak, nejprve sestavit GUI a až potom se pustit do datového modelu, nebo jakkoli jinak nám to v konkrétním projektu vyhovuje.)
Parsování XML má dvě části: součástí implementace třídy RSS jsou metody delegáta třídy NSXMLParser, jež už vlastně známe z prvního dílu našeho seriálu. Ty ovšem jen předávají požadavky objektům NSMutableDictionary, které se starají o vlastní parsování.
Stojí za to si znovu připomenout díl "Podtřídy, delegáti, vkládání...": tehdy jsme si řekli, že v Cocoa poměrně často užíváme jiných technik než vytváření podtříd, neboť si tak ušetříme práci. Nejinak tomu je i v tomto projektu: místo toho, abychom implementovali poměrně složité vlastní třídy pro datovou strukturu odpovídající hierarchické struktuře XML, prostě použijeme standardní knihovní kontejnery NSMutableDictionary a NSMutableArray, a potřebné accesory a speciální služby doplníme do třídy NSMutableDictionary prostřednictvím kategorie.
Metody delegáta
Dnes už bychom měli principu delegace rozumět mnohem lépe, než v prvém dílu: víme, že řada objektů Cocoa využívá delegátů – spolupracujících objektů, jež mohou implementovat některé z řady standardních metod. Nejinak tomu je i s třídou NSXMLParser: její instance při parsování dat ve formátu XML posílá svému delegátu zprávy pro každý prvek XML, na který narazí.
Nás pro naše účely zajímají pouze tři zprávy: informace o otvíracím tagu, informace o uzavíracím tagu, a informace o textovém obsahu. Ostatní metody tedy vůbec nebudeme implementovat; víme, že mechanismus delegace se standardně využívá tak, že řídící objekt posílá svému delegátu pouze ty zprávy, pro něž má delegát vhodné metody.
Vlastní implementace metod delegáta ve třídě RSS je triviální – prostě si vyžádáme zpracování odpovídající události od aktuální instance NSMutableDictionary (již objekt třídy RSS, jak víme od minula, udržuje v pomocné proměnné md). Metody, jež zpracovávají události "otevření tagu" a "uzavření tagu" navíc obě vracejí novou hodnotu, již dosazujeme zpět do md; to proto, že obě mohou měnit aktuální instanci NSMutableDictionary (např. otvírací tag může vytvořit vnořený objekt, je-li to zapotřebí). Celá implementace pak vypadá takto:
-(void)parser:p didStartElement:element namespaceURI:u qualifiedName:q attributes:a { md=[md ocs_startElement:element]; } -(void)parser:p didEndElement:e namespaceURI:u qualifiedName:q { md=[md ocs_endElement]; } -(void)parser:p foundCharacters:string { [md ocs_stringData:string]; }
Metody třídy NSMutableDictionary
Pro representaci dat ve formátu XML (odpovídajících v našem případě novinkám ze zdroje RSS) potřebujeme objekty, jež budou schopny obsahovat libovolná data pro dané textové klíče (jako NSMutableDictionary), v případě stejných klíčů vícenásobná (tedy jako NSMutableArray), ale přitom rozumějí našim zprávám ocs_startElement:, ocs_endElement a ocs_stringData: tak, jak potřebujeme. Ve většině objektových systémů by to znamenalo nutnou implementaci poměrně složité nové třídy; ne tak v Cocoa: zde prostě přímo použijeme NSMutableDictionary a NSMutableArray, a všechny potřebné nové služby doplníme pomocí kategorií.
Přidáme do projektu nový zdrojový soubor "XMLImportCategory.m" (a odpovídající hlavičkový soubor "XMLImportCategory.h"). Samozřejmě nezapomeneme přidat na začátek zdrojového souboru "RSS.m" direktivu #import "XMLImportCategory.h"; obsah tohoto hlavičkového souboru bude vypadat zhruba takto:
@interface NSMutableDictionary (XMLImportCategory) -(NSString*)ocs_currentElement; -(void)ocs_setCurrentElement:(NSString*)element; -(NSMutableDictionary*)ocs_backLink; -(void)ocs_nest:obj forKey:(NSString*)key; -(NSMutableDictionary*)ocs_startElement:(NSString*)element; -(NSMutableDictionary*)ocs_endElement; -(void)ocs_stringData:(NSString*)string; @end
První dvojice zpráv – vlastně jde o zcela standardní accesory – zajistí standardní přístup k "aktuálnímu elementu". Ten budeme potřebovat při parsování, proto, že zprávy o otevření nového tagu a o jeho obsahu přicházejí zvlášť – např. titulek "BBC" tedy dostaneme jako trojici zpráv: (i) otevření tagu "title", (ii) textový obsah "BBC", (iii) uzavření tagu. Musíme si tedy při otevření tagu zapamatovat jméno aktuálního elementu, a při přijetí textového obsahu jej použít.
Následující zpráva ocs_backLink zajišťuje také technickou pomůcku: ve struktuře vzájemně vnořených objektů potřebujeme, aby vnořený objekt obsahoval odkaz na objekt o úroveň výš: to proto, že když plnění vnořeného objektu dokončíme (tj. přijde zpráva o odpovídajícím uzavření tagu), musíme se opět "vrátit" o úroveň výš. Zpráva tedy vrátí nadřízený objekt (pokud takový existuje).
Nejsložitější je čtvrtá zpráva, ocs_nest:forKey: – jak uvidíme níže, budeme na implementaci odpovídající metody potřebovat celých patnáct řádků Objective C. Zpráva zajišťuje základní službu při sestavování struktury XML: do objektu, jenž ji přijme, vloží vnořený objekt. Přitom zajistí potřebné vedlejší funkce:
- kdykoli se vkládá NSMutableDictionary, nastaví se v něm korektně odkaz na nadřízený objekt (aby správně fungovala metoda ocs_backLink);
- pokud se vkládá string s tagem, pro nějž už v objektu nějaký string je, oba texty se zřetězí;
- pokud se vkládá NSMutableDictionary s tagem, pro nějž už v objektu je string, string se zapomene. To proto, že NSXMLParser nám jako textové objekty předává i oddělovače mezi tagy (např. konce řádků);
- z téhož důvodu naopak, pokud se vkládá string s tagem, pro nějž už v objektu je NSMutableDictionary, nic se nevloží;
- konečně, vkládáme-li NSMutableDictionary s tagem, pro nějž už v objektu je jiný NSMutableDictionary, vytvoří se na jejich nový objekt třídy NSMutableArray, obsahující oba dva (resp. všechny, je-li jich ještě více).
Poslední trojice zpráv pak již využije toto rozhraní k poměrně snadné implementaci služeb, potřebných pro třídu RSS – otavření nového tagu (ocs_startElement:), vložení textových dat (ocs_stringData:) a uzavření tagu (ocs_endElement).
Implementace kategorie
Jak je z výše uvedeného rozboru vidět, stačí nám jediná kategorie třídy NSMutableDictionary (pokud by ovšem úloha byla složitější, samozřejmě bychom pomocí dalších kategorií mohli potřebným způsobem rozšířit i služby tříd NSMutableArray, případně NSString). Ukážeme si implementaci jednotlivých metod postupně, a vždy si podrobněji vysvětlíme použitou techniku.
-(NSString*)ocs_currentElement { return [self objectForKey:@"."]; } -(void)ocs_setCurrentElement:(NSString*)element { if (element) [self setObject:element forKey:@"."]; else [self removeObjectForKey:@"."]; }
Accesory pro aktuální element jsou triviální: element je prostě uložen do objektu pod klíčem ".".
-(NSMutableDictionary*)ocs_backLink { // trick so as the buggy description works! return [[self objectForKey:@"<"] nonretainedObjectValue]; }
Podobně odkaz na nadřízený objekt je uložen pod klíčem "<". Je zde však použit malý trik: jde o to, že pokud bychom ukládali odkaz na nadřízený objekt přímo (a pak jej také přímo vraceli příkazem return [self objectForKey:@"<"]), vznikl by v datové struktuře cyklus (nadřízený objekt A obsahuje pod nějakým klíčem K vnořený objekt B, ten obsahuje pod klíčem "<" nadřízený objekt A, ten obsahuje pod klíčem K objekt B, ten...). Z hlediska vlastního kódu i samotné třídy NSMutableDictionary by to nebyl velký problém; vzpomeňme si však na díl "Vznik a zánik objektů", kde jsme si vysvětlili, že a proč cykly mohou vést k tomu, že se nějaký objekt nikdy neuvolní – pokud NSMutableDictionary používá pro udržení odkazů na vložené objekty retain (a tak tomu skutečně je).
Abychom se tomu vyhnuli, namísto přímého vložení odkazu na nadřízený objekt proto vložíme jeho adresu nepřímo, zakódovanou do samostatného objektu třídy NSValue (viz níže, valueWithNonretainedObject:). Zpět původní hodnotu (tedy přímo adresu nadřízeného objektu) dostaneme právě zprávou nonretainedObjectValue.
-(void)ocs_nest:obj forKey:(NSString*)key { if (!obj || !key) return; // stay on the safe side BOOL newIsDict=[obj isKindOfClass:[NSMutableDictionary class]]; id alreadyThere=[self objectForKey:key]; if (newIsDict) [obj setObject:[NSValue valueWithNonretainedObject:self] forKey:@"<"]; if (!alreadyThere) [self setObject:obj forKey:key]; // new value else if ([alreadyThere isKindOfClass:[NSString class]]) // override or concat if (newIsDict) [self setObject:obj forKey:key]; // override else [self setObject:[alreadyThere stringByAppendingString:obj] forKey:key]; else if (newIsDict) // if somtehing's already there, only dict gets added if ([alreadyThere isKindOfClass:[NSMutableDictionary class]]) // 2nd object [self setObject:[NSMutableArray arrayWithObjects:alreadyThere,obj,nil] forKey:key]; else if ([alreadyThere isKindOfClass:[NSMutableArray class]]) // 3rd or more [alreadyThere addObject:obj]; }
Metoda ocs_nest:forKey: implementuje hlavní funkčnost celého sestavování struktury kontejnerů, jak jsme si ji popsali v bodech na konci minulého odstavce. Podívejme se na ni řádek po řádku:
- nejprve se jen postaráme o to, aby – pokud někdo zprávu pošle bez hodnoty obj nebo key – se nic nestalo;
- pak si v pomocné proměnné newIsDict zapamatujeme, zda je vkládaný objekt instancí třídy NSMutableDictionary – vzpomínáte ještě na díl "Základní služby objektů", odstavec "Hierarchie tříd"?
- do další pomocné proměnné alreadyThere uložíme objekt, který již pro daný key máme (existuje-li takový);
- pokud je vkládaný objekt instancí třídy NSMutableDictionary, uložíme do něj odkaz na nadřízený objekt (ohledně důvodu použití třídy NSValue viz diskusi implementace metody ocs_backLink výše);
- pokud jsme pro daný key nic neměli, prostě uložíme nový objekt;
- je-li v alreadyThere text, nový text za něj připojíme, nebo jej nahradíme novým NSMutableDictionary;
- jinak pokračujeme jen je-li nový objekt NSMutableDictionary (kdyby to byl text, chceme jej ignorovat);
- pokud byl v alreadyThere také NSMutableDictionary, vytvoříme NSMutableArray s oběma;
- jinak musí být alreadyThere pole objektů, a nový prostě přidáme na konec.
-(NSMutableDictionary*)ocs_startElement:(NSString*)element { NSString *curr=[self ocs_currentElement]; if (!curr) { [self ocs_setCurrentElement:element]; // if there was none, record it return self; } else { // otherwise has to nest NSMutableDictionary *md=[NSMutableDictionary dictionary]; [self ocs_nest:md forKey:curr]; [md ocs_setCurrentElement:element]; return md; } }
Při otvírání nového tagu se podíváme na ocs_currentElement, zda je právě nějaký tag otevřený. Pokud tomu tak není, prostě si ten, jenž právě přišel, zapamatujeme; jinak vytvoříme vnořený NSMutableDictionary a vrátíme jej (sestoupíme tedy o úroveň níže).
-(NSMutableDictionary*)ocs_endElement { NSMutableDictionary *new=[self ocs_currentElement]?self:[self ocs_backLink]; [new ocs_setCurrentElement:nil]; // unset current return new; // if there was no current, drop back }
Analogicky při zavírání tagu ověříme, zda je právě nějaký tag otevřený. Pokud tomu tak není, vystoupíme o úroveň výš (ocs_backLink). Pak jen zrušíme otevřený tag.
-(void)ocs_stringData:(NSString*)string { string=[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; [self ocs_nest:string forKey:[self ocs_currentElement]]; }
Poslední metoda je nejjednodušší: jen odřízneme případné oddělovače na okrajích textu (stringByTrimmingCharactersInSet:), a pak použijeme metodu ocs_nest:forKey: pro vložení výsledku do aktuálního objektu.
A to je celé: kód, který jsme si ukázali dnes, převede data ve formátu XML na strukturu kontejnerů; kód, jenž jsme viděli minule, obsahuje accesory, jež z této struktury získají titulek a seznam novinek. Příště se tedy pustíme do přípravy grafického uživatelského rozhraní, jež tyto informace patřičným způsobem zobrazí.
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