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 - Třídy Foundation Kitu (2)
9. února 2005, 00.00 | Minule jsme si ukázali přehled všech tříd Foundation Kitu a stručně jsme probrali účel a základní využití většiny z nich. Nyní se soustředíme jen na ty nejčastěji využívané; zato se na ně podíváme podrobněji a ukážeme si i zcela konkrétní příklady použití.
Minule jsme si ukázali přehled všech tříd Foundation Kitu, a stručně jsme probrali účel a základní využití většiny z nich. Nyní se soustředíme jen na ty nejčastěji využívané; zato se na ně podíváme podrobněji a ukážeme si i zcela konkrétní příklady použití.
Na prvém místě by samozřejmě měly být služby samotné kořenové třídy NSObject; těmi jsme se však v našem seriálu již zabývali, takže přejdeme rovnou k asi nejužívanějším třídám (vedle NSStringu, který si necháme na příště) vůbec: ke kontejnerům. Už minule jsme se zmínili o tom, že kontejnery Cocoa jsou navrženy tak šikovně, že jimi dokážeme bez problémů a pohodlně pokrýt předlouhou řadu datových nároků většiny aplikací, aniž bychom měli zapotřebí definovat vlastní třídy; dnes si ukážeme několik praktických příkladů.
NS(Mutable)Array
Pro ilustraci základního využití tříd NS(Mutable)Array si ukážeme (nikoli ideální) implementaci triviálního N-árního stromu, tj. datové struktury, ve které má každý objekt libovolně mnoho 'následníků'. Konkrétní příklad takové struktury vidíme na následujícím obrázku:
V takové Javě či v C++ (a ostatně, ani v Objective C bez beztypových kontejnerů) bychom takovouto strukturu nemohli vytvořit bez vlastní nové třídy; v Objective C s využitím beztypových kontejnerů Foundation Kitu je to ale snadné, a bohatě na to stačí samotná třída NSArray (přesněji řešeno, NSMutableArray, protože chceme aby strom byl dynamicky měnitelný).
Pro implementaci využijeme jednoduchoučkého triku, umožněného právě beztypovostí kontejnerů: každý uzel stromu bude representován jedním objektem třídy NSMutableArray, s tím, že skutečný "obsah" uzlu (tedy libovolný objekt, na našem obrázku representovaný písmenem A-J) bude vždy prvým objektem v poli. Ostatní objekty – budou-li takové – pak budou přímo podřízené uzly. Použijeme-li tedy celkem přirozený rekursivní zápis, využívající závorek pro representaci pole (takže prázdné pole bychom mohli vyjádřit výrazem "()", pole obsahující jediný objekt X by se dalo znázornit jako "(X)", pole obsahující objekty X a Y pak "(X,Y)"), mohli bychom celý strom z minulého obrázku zapsat jako
(A,(B,(E),(F)),(C),(D,(G),(H),(I),(J)))
Ukažme si nyní možnou implementaci základních služeb pro práci se stromy – samozřejmě, že je implementujeme prostřednictvím kategorie, jako nové služby třídy NSMutableArray:
@implementation NSMutableArray (PlainTreeServices) +newTree:contents { return [self arrayWithObject:contents]; } ...
Prvá metoda je naprosto triviální: vytvoří prostě nový strom (tedy novou instanci třídy NSMutableArray) s jediným uzlem, obsahujícím zadaný objekt. Je zřejmé, že a proč půjde o metodu třídy: žádnou instanci dosud nemáme, tu teprve budeme vytvářet. Proměnná self zde tedy representuje samotnou třídu.
Díky poloautomatickému garbage collectoru není zapotřebí žádná služba pro zrušení stromu: celá datová struktura bude zrušena automaticky, jakmile ji již nikdo nebude potřebovat, a podle potřeby lze bez omezení užívat služeb retain/release/autorelease. Kořen stromu z minulého obrázku, obsahující objekt A – ať je tímto objektem cokoli – bychom tedy mohli vytvořit příkazem
id ourTree=[NSMutableArray newTree:A];
za předpokladu (se kterým budeme pracovat i nadále), že objekty A-J, jež budou uloženy uvnitř stromu, již jsou pod odpovídajícími jmény k dispozici.
Následující dvojice služeb contents a childs zajišťuje přístup k objektu, uloženém v daném uzlu, a k jeho podřízeným uzlům. Zde již samozřejmě půjde o metody instance – odpovídající zprávy budeme zasílat přímo uzlům, o jejichž obsah (či podřízené uzly) máme zájem:
... -contentsOf { return [self objectAtIndex:0]; } -(NSArray*)childs { return [self subarrayWithRange:NSMakeRange(1,[node count]-1)]; } ...
Proměnná self zde tedy representuje uzel jako takový, konkrétní instanci třídy NSMutableArray. Obě použité zprávy jsou celkem zřejmé – objectAtIndex: prostě vrátí objekt z pole na daném indexu, subarrayWithRange: vytvoří nové pole, jehož prvky jsou částí prvků pole, jemuž tuto zprávu posíláme. Zde snad stojí za samostatnou zmínku speciální typ NSRange, jejž Cocoa využívá kdykoli pracujeme s rozsahy indexů: jde o klasickou "plaincéčkovou strukturu" s položkami location a length (prvý index a délka); NSMakeRange pak je jen standardní inline makro, jež nám umožňuje tyto struktury pohodlně vytvářet.
Javští programátoři se možná pozastaví nad tím, proč jde o obyčejnou strukturu a ne o objekt: inu, to je kvůli efektivitě: u natolik triviálního objektu, jakým je rozsah indexů, by dynamické služby objektového systému (jako je možnost přesměrování zpráv, dědičnost, ....) nepřinesly žádnou výhodu, jež by stála za hovor; práce s jednoduchou strukturou je přitom nesrovnatelně efektivnější (mj. díky tomu, že pro ni není obecně třeba alokovat paměť – na rozdíl od objektu se takováto jednoduchá struktura vejde přímo do registrů procesoru). Obdobné struktury, s nimiž se seznámíme později, jsou třeba NSPoint či NSRect (obecný bod či obdélník v nějakém souřadném systému).
Vraťme se k implementaci naší stromové struktury – potřebujeme ještě přinejmenším jednu metodu, takovou, jež do již existujícího stromu vloží nový uzel:
... -addChild:contents { id child=newTree(contents); [node addObject:child]; return child; } ...
Služba addChild: prostě přidá nový podřízený uzel se zadaným obsahem pod příjemce. Přitom rovnou vrátí nově přidaný uzel, takže jej ihned můžeme použít v dalším kódu. Ukažme si třeba příkazy, jež by vytvořily kompletní strom z úvodního obrázku:
id o=[newTree addChild:B]; [o addChild:E]; [o addChild:F]; [newTree addChild:C]; [o=[newTree addChild:D] addChild:G]; [o addChild:H]; [o addChild:I]; [o addChild:J];
Nu, a to je vlastně vše: výše uvedených asi deset řádků knihovního kódu bohatě stačí pro základní práci s datovými stromy: nebylo třeba vytvářet žádné nové třídy – něco podobného je možné jen v plně objektovém systému s dobře navrženými knihovnami! Přitom se vyplatí si uvědomit, že díky standardním službám Foundation Kitu jsme navíc úplně "zadarmo" dostali spoustu dalších možností:
- již jsme se zmínili, že datové stromy (stejně jako všechny ostatní objekty v API Cocoa) jsou díky garbage collectoru automaticky odstraněny jakmile je již nikdo nepotřebuje; to umožňuje i jejich korektní sdílení mezi různými moduly;
- sdílení bude pracovat zcela korektně i v případě, že moduly jsou v různých adresových prostorech nebo vůbec na různých počítačích (takto sestavené datové stromy můžeme okamžitě a bez jakéhokoli dalšího programování např. předávat mezi klientem a serverem prostřednictvím tzv. distribuovaných objektů);
- do stromů můžeme ukládat bez omezení naprosto jakékoli objekty, a tyto objekty mohou být bez omezení sdíleny mezi různými uzly nebo i mezi různými stromy;
- stromy můžeme okamžite vzájemně porovnávat standardní službou isEqual: (takže "[tree1 isEqual:tree2]" bude pravda právě v případě, že obsahy obou stromů jsou přesně ekvivalentní);
- ihned a bez psaní dalšího kódu můžeme vytvářet snímky celých stromů (např. pro implementaci standardní funkce "undo") standardní zprávou copy; díky paradigmatu proměnných a neproměnných objektů je přitom automaticky zajištěno, že obsahy jednotlivých uzlů se budou duplikovat jen je-li to skutečně zapotřebí (pozor, určitě si nezapomeňte přečíst u linkovaného článku diskusi!);
- stromy můžeme okamžitě a bez jakéhokoli dalšího programování ukládat do souborů a číst z nich; pokud budou objekty uložené v uzlech textovými řetězci (nebo jinými objekty ze skupiny obecných datových typů), bude možné je ukládat do textových souborů, jež lze číst/editovat externě;
- stromy "samy od sebe" umějí vypsat svůj obsah ve formátu využívajícím "závorkovou" notaci, popsanou výše – stačí použít např. standardní službu Foundation Kitu "NSLog(@"%@",tree)", a obsah stromu byde vypsán zcela korektně;
- stromy můžeme volně ukládat do všech kontejnerů, včetně kontejnerů využívajících hashování (např. NSSet, NSDictionary);
- kterýkoli uzel stromu může stejně dobře zároveň sloužit jako součást celého stromu, i stát zcela samostatně a representovat svůj podstrom – to se v praxi velmi často hodí, a my tuto službu získali bez jakéhokoli programování navíc. Takovéto podstromy mohou být opět bez jakéhokoli omezení sdíleny.
Ukažme si pro zajímavost ještě možné implementace některých dalších služeb nad stromem. Prvou a nejjednodušší z nich by mohlo být posčítání všech objektů uvnitř stromu; pro procházení využijeme obecnou třídu NSEnumerator:
... -(int)deepTreeCount { int count=1; // jeden objekt je v tomto uzlu NSEnumerator *en=[[self childs] objectEnumerator]; id tree; while (tree=[en nextObject]) count+=[tree deepTreeCount]; return count; } ...
O moc jednodušeji by to již skutečně nešlo. Samozřejmě, že namísto iterátoru (NSEnumerator) bychom mohli stejně snadno využít indexy; iterátor je však o něco pohodlnější, snižuje pravděpodobnost chyby, a ve složitějším kódu přináší další výhodu – dokud jej užíváme, je procházené pole "přidrženo" službou retain, takže není možné, aby nám někdo jiný sdílená data uvolnil dokud s nimi pracujeme. Při použití indexů bychom se o to museli postarat sami, iterátor to zajistí zcela automaticky.
O nic složitější nebude ani vyhledání zadaného objektu uvnitř stromu: následující metoda vrátí buď uzel, obsahující zadaný objekt, nebo hodnotu nil pokud ve stromě žádný takový uzel není:
... -nodeWith:contents { if ([self contents] isEqual:contents]) return tree; NSEnumerator *en=[[self childs] objectEnumerator]; id tree; while (tree=[en nextObject]) if ((tree=[tree nodeWith:contents])!=nil) return tree; return nil; } @end
NS(Mutable)Set, NSCountedSet
Jako jednoduchoučkou ukázku služeb knihovní třídy NSSet si předvedeme implementaci metody pro "uniquing": odpovídající zprávu můžeme zaslat jakémukoli poli, obsahujícímu naprosto libovolnou skupinu objektů. Metoda vrátí jiné pole, jež bude obsahovat tytéž objekty, ale bez duplicit – každý objekt v něm bude uložen nanejvýš jednou.
Snad každý programátor s rozsáhlejšími zkušenostmi potvrdí, že obdobnou službu potřebujeme v praxi dost často. V klasických API se to většinou řeší tak, že ji pro každý případ programujeme znovu, protože v konkrétních případech – kde není zapotřebí plná obecnost funkce nad zcela libovolnými objekty – bývá její implementace mnohem snazší, obvykle zabere mezi pěti až dvaceti řádky kódu, podle konkrétní situace a sady omezení, jež v ní platí.
Zkuste nejprve hádat, kolik řádků zabere zcela obecná implementace bez jakýchkoli omezení v API Cocoa! Ano, skutečně – je to právě jeden jediný řádek (v těle metody – plus nějaká ta standardní vata okolo); jednodušeji by to už opravdu ani při nejlepší vůli nešlo:
@implementation NSArray (RemoveDups) -(NSArray*)removeDups { return [[NSSet setWithArray:self] allObjects]; } @end
Příklad použití této služby by mohl vypadat třeba takto (zprávu posíláme přímo z ladicího programu gdb):
(gdb) po a (a, b, a, xyz, xyz, (nested, array), a, b, a, xyz, xyz, (nested, array)) (gdb) po [a removeDups] (xyz, b, a, (nested, array)) (gdb)
V tomto případě pole obsahovalo jen textové řetězce a vnořená pole; stejně dobře by však funkce pracovala nad libovolnými objekty (včetně např. datových stromů z minulého odstavce). Je také vhodné si uvědomit, že díky hashování je tato funkce velmi efektivní – pravděpodobně ne tolik, jako kdybychom ji napsali přímo a pilování jejího algoritmu věnovali hodně času, ale určitě mnohem, mnohem efektivnější než cokoli, co lze napsat byť za stonásobek těch asi deseti sekund, jež byly zapotřebí pro výše uvedenou implementaci...
Jako ilustraci služeb třídy NSCountedSet (a několika dalších) si pro změnu ukážeme nejen výňatek kódu, ale celý program, který načte daný textový soubor, a provede jeho frekvenční analýzu. Kompletní zdrojový kód – bez zvláštních služeb pro vstup či výstup, ale s celou analýzou textu včetně třídění výsledků podle četnosti – jsem psal ani ne čtvrt hodiny, a stačilo k tomu třicet zdrojových řádků: to je Cocoa.
#import <Foundation/Foundation.h> int cmpWithSet(id left,id right,NSCountedSet *freq) { return [freq countForObject:right]-[freq countForObject:left]; } int main (int argc, const char *argv[]) { [[NSAutoreleasePool alloc] init]; NSString *fname=[NSString stringWithCString:argv[1]]; NSString *data=[NSString stringWithContentsOfFile:fname]; NSLog(@"Scanning \"%@\" (%d bytes)...",fname,[data length]); if (data) { NSScanner *sc=[NSScanner scannerWithString:data]; NSCharacterSet *wordDelims=[NSCharacterSet characterSetWithCharactersInString: @" ,.?!;:\"\'/-()0123456789*#@\\\r\n"]; NSCountedSet *freq=[NSCountedSet set]; NSArray *a; int q,i; while (![sc isAtEnd]) { NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init]; NSString *str; [sc scanCharactersFromSet:wordDelims intoString:NULL]; // skip delims if ([sc scanUpToCharactersFromSet:wordDelims intoString:&str]) if ([str length]>3) [freq addObject:str]; [pool release]; } NSLog(@"done, found %d words",[freq count]); a=[[freq allObjects] sortedArrayUsingFunction:(int(*)(id,id,void*))cmpWithSet context:freq]; NSLog(@"sorted, first ten:"); if ((i=10)>[a count]) i=[a count]; for (q=0;q<i;q++) NSLog(@"%@ (%d)",[a objectAtIndex:q],[freq countForObject:[a objectAtIndex:q]]); } else NSLog(@"Cannot properly open \"%@\"",fname); return 0; }
Jistěže řada drobností by se dala vylepšit (např. minimální délka slova by měla být parametrizovatelná, a ne fixní 4; mělo by být možné zvolit kódování českých znaků na vstupu, pro výstup by se mělo využít lepší formátování, než triviální služba NSLog – jež, jak uvidíme níže, zobrazuje znaky Unicode dost nečitelným způsobem – apod.). Přesto je program již v této podobě velmi použitelný.
Podívejme se jen v rychlosti na použité služby:
- metody addObject: a countForObject: třídy NSCountedSet dělají přesně to, co jejich názvy naznačují – prvá vloží objekt, druhá zjistí pro daný objekt kolikrát byl do množiny vložen;
- metody stringWithCString: a stringWithContentsOfFile: třídy NSString vytvoří nový textový objekt na základě "plaincéčkového" řetězce (tedy pole znaků), respektive na základě obsahu zadaného souboru;
- třída NSCharacterSet representuje zcela obecnou množinu znaků Unicode; její vytvoření na základě explicitně zadaného seznamu znaků pomocí zprávy characterSetWithCharactersInString: je snad zřejmé;
- o třídě NSScanner jsme se stručně zmínili minule: scannerWithString: vytvoří parser nad zadaným textem, scanCharactersFromSet:intoString: přejede všechny znaky ze zadané množiny (a jelikož je argumentem intoString: hodnota NULL, nikam je neukládá), zatímco scanUpToCharactersFromSet:intoString: projde všechny znaky, jež nepatří do zadané množiny – a uloží je do stringu, jenž je argumentem intoString:;
- metoda sortedArrayUsingFunction: setřídí prvky v poli, přičemž pro porovnání dvou různých prvků použije zadanou funkci (Cocoa nabízí několik dalších třídicích primitiv, nezaložených na užití funkcí, nýbrž na zasílání zpráv; ty se nám zde však tak dobře nehodí).
Za samostatnou zmínku pak stojí už jen použití vnořeného autorelease poolu v cyklu while: to je jednoduché – vytvoříme-li nový pool příkazem "[[NSAutoreleasePool alloc] init]", všechny "autoreleasované" objekty se automaticky ukládají právě do tohoto poolu. Jakmile pak tento pool zrušíme ("[pool release]"), všechny autoreleasované objekty se uvolní. To má smysl v případě, kdy se bude cyklem procházet mnohokrát, aby spotřeba paměti nebyla příliš velká.
Programátoři v C++ a jiných assemblerech ☺, stejně jako na opačném konci palety jazyků uživatelé Smalltalku, patrně budou pochybovat o efektivitě takto napsaného programu. Proto jsem mu – na dost letitém počítači, osazeném procesorem G4 s frekvencí pouhých 400 MHz – na zkoušku podstrčil text Bible, který má přes čtyři megabyty. Frekvenční analýza zabrala méně než půl minuty, třídění výsledků trvalo cca jednu sekundu – jak je dobře vidět z časových značek, jež služba NSLog automaticky používá:
Nov 15 05:00:52 Scanning "Cznwt.txb" (4325411 bytes)... Nov 15 05:01:19 done, found 49413 words Nov 15 05:01:20 sorted, first ten: Nov 15 05:01:20 jeho (4912) Nov 15 05:01:20 \U00afekl (3986) Nov 15 05:01:20 jsem (3539) Nov 15 05:01:20 jako (3468) Nov 15 05:01:20 Jehova (3053) Nov 15 05:01:20 kte\U00af\U00cc (2550) Nov 15 05:01:20 jejich (2195) Nov 15 05:01:20 kter\U02dd (2138) Nov 15 05:01:20 bude (1873) Nov 15 05:01:20 p\U00afed (1846)
NS(Mutable)Dictionary
Hash tabulky, jež Foundation Kit nabízí prostřednictvím tříd NSDictionary a NSMutableDictionary patří mezi nejužívanější služby vůbec: jsou totiž nesmírně pohodlné a flexibilní. Připomeňme si minulý příklad, ve kterém jsme využili NSCountedSet pro frekvenční analýzu daného textu. Dnes si ukážeme analogický příklad, ve kterém nám poslouží třída NSMutableDictionary pro vytvoření rejstříku. Podobně jako minule, zároveň si ukážeme funkci a služby několika dalších tříd – příkladem bude kompletní program.
Náš prográmek vytvoří kompletní index všech souborů HTML v zadané složce a všech složkách vnořených: pro každé slovo vytvoří seznam všech dokumentů, ve kterých je toto slovo využito. Program bude o něco málo luxusnější než minulý příklad; obsahuje např. dekódování argumentů příkazové řádky. Nejprve se podíváme na zdrojový text (kompletní program zabere méně, než 60 řádků), a pak si některé příkazy vysvětlíme podrobněji:
#import <Foundation/Foundation.h> int main (int argc, const char *argv[]) { [[NSAutoreleasePool alloc] init]; NSCharacterSet *wordDelims=[NSCharacterSet characterSetWithCharactersInString: @" ,.?!;:\"\'/-()0123456789*#@\\\r\n"]; NSFileManager *fm=[NSFileManager defaultManager]; NSUserDefaults *df=[NSUserDefaults standardUserDefaults]; NSString *ifolder=[df objectForKey:@"input"],*ofile= [df objectForKey:@"output"]; // *1* NSMutableDictionary *index=[NSMutableDictionary dictionary]; NSMutableString *output=[NSMutableString string]; int minword=3,totalf=0,totalw=0,totalb=0; id en,o; if (!ifolder || !ofile) { printf("IndexHTML [-minword <min.word size>] -input <input folder> -output <output file>\n"); exit(0); } if (o=[df objectForKey:@"minword"]) minword=[o intValue]; for (en=[fm enumeratorAtPath:ifolder];o=[en nextObject];) if ([[o pathExtension] isEqual:@"html"]) { // *2* NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init]; NS_DURING // *3* NSString *fname=[o lastPathComponent]; NSAttributedString *htmlContents=[[[NSAttributedString alloc] initWithPath: [ifolder stringByAppendingPathComponent:o] documentAttributes:NULL] autorelease]; // *4* NSString *contents=[htmlContents string]; NSScanner *sc=[NSScanner scannerWithString:contents]; int len=[[en fileAttributes] fileSize],current=totalw,currentItems=[index count]; NSLog(@"Scanning \"%@\" (%d bytes)...",fname,len); totalf++; totalb+=len; while (![sc isAtEnd]) { NSString *word; [sc scanCharactersFromSet:wordDelims intoString:NULL]; // skip any delimiters if ([sc scanUpToCharactersFromSet:wordDelims intoString:&word] && [word length]>minword) { NSMutableSet *s=[index objectForKey:word]; // *5* if (!s) [index setObject:s=[NSMutableSet set] forKey:word]; [s addObject:fname]; totalw++; } } NSLog(@"...%d words (%d new items)",totalw-current,[index count]-currentItems); NS_HANDLER NSLog(@"*** aborted since %@",[localException reason]); NS_ENDHANDLER [pool release]; } NSLog(@"Scanned %d files (%d words, %d bytes)",totalf,totalw,totalb); for (en=[[[index allKeys] sortedArrayUsingSelector:@selector(compare:)] objectEnumerator]; o=[en nextObject];) // *6* [output appendFormat:@"%@: %@\n",o,[[index objectForKey:o] allObjects]]; if (![output writeToFile:ofile atomically:NO]) NSLog(@"!!! Can't write %@",ofile); NSLog(@"Index successfully written to \"%@\"",ofile); return 0; }
Na řádku s komentářem *1* začíná zpracování vstupních argumentů. Nemáme již zde dost místa pro podrobný popis služeb třídy NSUserDefaults; za stručnou zmínku však stojí alespoň to, že kromě dekódování příkazového řádku zajišťuje tato třída i přístup k velmi obecné databázi uživatelských předvoleb (jde o soubory XML ve složce ~/Library/Preferences). Bez dalšího programování máme tedy k dispozici nejen příkazový řádek, ale můžeme i fixovat standardní hodnoty argumentů v této databázi, a náš program je automaticky využije.
Hlavní důvod, proč se zde třídou NSUserDefaults vůbec zabýváme, však je ilustrace jedné z velmi příjemných vlastností API Cocoa, kterou je důsledné využívání polymorfismu. Všimněte si, že pro získání hodnoty požadovaného argumentu z objektu NSUserDefaults slouží přesně táž zpráva (objectForKey:), jako pro získání hodnoty požadovaného klíče z objektu NSDictionary (na řádku s komentářem *5*, nebo na řádku za komentářem *6*). To přináší dvě obrovské výhody:
- doba, potřebná k naučení se API Cocoa, je mnohem menší, než doba, potřebná pro naučení se jiných – i daleko chudších – API;
- často se hodí i přímé využití polymorfismu pro větší flexibilitu kódu.
Obsah řádku *2* je vše, co potřebujeme pro vyhledání všech souborů, jež budeme indexovat: obsah příkazu for zajistí procházení všech souborů uvnitř složky ifolder a složek v ní vnořených, a následující příkaz if z nich vybere jen soubory s příponou HTML.
Makra NS_DURING (*3*), NS_HANDLER a NS_ENDHANDLER jsou v Cocoa standardní obsluhou výjimek (existuje i alternativní sada jazykových služeb řízených direktivami @try, my však pro jednoduchost zůstaneme u klasických maker). Funkčně tedy odpovídají kombinaci try/catch z C++, jsou však mnohem efektivnější. V našem jednoduchém prográmku slouží k tomu, aby – pokud při zpracování některého souboru dojde k výjimce – nebyl program ukončen, ale aby indexování pokračovalo dalším souborem.
Na řádku *4* používáme třídu NSAttributedString, která dokáže krom řady jiných služeb také korektně načíst soubor ve formátu HTML a vrátit jeho textový obsah jako standardní string (toho využijeme hned na následujícím řádku). Pro vyhledání jednotlivých slov v jeho obsahu použijeme NSScanner přesně stejně, jako v minulém příkladu.
Tři řádky počínaje komentářem *5* obsahují vlastní indexování. Jeho logika je jednoduchá: nejprve z objektu NSMutableDictionary (který je uložen v proměnné index) získáme objekt, jehož klíčem je dané slovo. Pokud takový objekt dosud neexistuje – protože jde o první výskyt daného slova – vytvoříme jej (jako prázdný NSMutableSet), a ihned jej – s daným slovem jako klíčem – vložíme do indexu (to je obsahem druhého řádku). Třetí řádek je triviální, prostě do objektu NSMutableSet vloží jméno souboru, ve kterém jsme dané slovo nalezli.
Je snad zřejmé, že tímto způsobem nakonec v proměnné index vybudujeme skutečný index, v němž klíči budou jednotlivá slova, a odpovídajícími hodnotami množiny, obsahující jména všech dokumentů, ve kterých se to které slovo vyskytuje. Mimochodem, malý kvíz pro pozorné čtenáře: proč jsme pro seznamy souborů použili třídu NSMutableSet, a ne třídu NSMutableArray?
Na konci běhu programu bychom sice mohli index vypsat přímo (příkazem "[index writeToFile:ofile...]"), jenže pak by slova nebyla setříděna podle abecedy. Proto vytvoříme pomocný NSMutableString, do kterého na pouhých dvou řádcích (*6* a následující) vygenerujeme výstupní seznam slov a odpovídajících souborů v abecedním pořadí. Srovnejte příkaz pro třídění (sortedArrayUsingSelector:) s obdobným příkazem z minulého příkladu – tentokrát využíváme dynamického systému Objective C, a prostě uvedeme zprávu, jejíž pomocí se mají při třídění slova porovnávat (je jí standardní zpráva compare:, jíž v API Cocoa lze srovnat libovolné dva objekty, nad nimiž je definována relace menší/větší).
Shrnutí
Dnes jsme si ukázali několik praktických ukázek využití standardních tříd NSArray, NSEnumerator, NSSet, NSCountedSet, NSDictionary a – bez podrobnějšího výkladu – také NSScanner, NSCharactersSet, NSUserDefaults či NSString. Příště se ještě vrátíme ke třídě NSString, jež je snad nejpoužívanější třídou Foundation Kitu vůbec.
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