Nastal čas na kakao - Třídy Foundation Kitu (2) - MujMAC.cz - Apple, Mac OS X, Apple iPod

Odběr fotomagazínu

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:

Soutěž

Sponzorem soutěže je:

IDIF

 

Jaký fotograf/ka získal/a cenu za nejpopulárnější příspěvek v Nikon Photo Contest?

V dnešní soutěži hrajeme o:

Seriály

Více seriálů



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):

Tématické zařazení:

 » Rubriky  » Informace  

 » Rubriky  » Agregator  

 » Rubriky  » Začínáme s  

 

 

 

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: