Druhé Objective C: nový příkaz cyklu - 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:

Seriály

Více seriálů



Informace

Druhé Objective C: nový příkaz cyklu

14. února 2008, 10.00 | Příjemnou novinkou Objective C 2.0 je také speciální příkaz cyklu, umožňující přímo procházet objekty uložené v libovolném typu kontejneru.

Příjemnou novinkou Objective C 2.0 je také speciální příkaz cyklu, umožňující přímo procházet objekty uložené v libovolném typu kontejneru. Již v Objective C 1.0 nebyl zásadní problém si podobnou službu doplnit pomocí makra, podobného např. tomuto:

#define forall(variable,enumerable)
  for (id variable,enumerator=[enumerable objectEnumerator];
      (variable=[enumerator nextObject]);)

Jistou nevýhodou ovšem bylo to, že nebylo možné deklarovat proměnnou cyklu s využitím specifikace typu (např. NSString* nebo id<Protokol>).

Příkaz for/in

Objective C 2.0 nyní obsahuje příkaz podobný, ale flexibilnější a efektivnější. Jeho syntaxe je jednoduchá:

for (variable in enumerable) ...

přičemž samozřejmě variable může i nemusí obsahovat i typ, jak je samozřejmé už od dob C99; možné tedy jsou obě varianty

for (NSString *s in pole) ...
NSString *s;
for (s in pole) ...

Příkaz lze využít s kterýmkoli z kontejnerů Foundation – ať již jde o pole, množinu, nebo třeba slovník. U těch je třeba si dát pozor na to, že zatímco NSEnumerator vytvořený výše uvedeným standardním způsobem procházel objekty, nový příkaz prochází (poněkud rozumněji :)) klíče:

iMac24% >q.m
#import <Cocoa/Cocoa.h>
int main() {
id d=[NSDictionary dictionaryWithObjectsAndKeys:
  @"o1",@"k1",@"o2",@"k2",nil];
NSLog(@"Enumerator:");
for (id o,en=[d objectEnumerator];o=[en nextObject];)
  NSLog(@"  %@",o);
NSLog(@"For/in:");
for (NSString *s in d) NSLog(@"  %@",s);
return 0;
}
iMac24% cc -fobjc-gc-only -std=gnu99 -framework Cocoa q.m && ./a.out
2007-11-07 01:02:06.203 a.out[3413:10b] Enumerator:
2007-11-07 01:02:06.210 a.out[3413:10b]   o1
2007-11-07 01:02:06.210 a.out[3413:10b]   o2
2007-11-07 01:02:06.211 a.out[3413:10b] For/in:
2007-11-07 01:02:06.211 a.out[3413:10b]   k1
2007-11-07 01:02:06.211 a.out[3413:10b]   k2
iMac24% 

(Mimochodem povšimněte si, že jsme si tentokrát nechali od cesty obligátní "[NSAutoreleasePool new]" na začátku testu; namísto toho jsme si vyžádali běh programu s garbage collectorem pomocí přepínače -fobjc-gc-only.)

Chceme-li zajistit kupříkladu procházení pole odzadu, můžeme v příkazu for/in použít samozřejmě i enumerátor:

for (id o in [pole reverseObjectEnumerator]) ...

Je vhodné se zmínit také o tom, že příkaz for/in úmyslně nedovoluje, aby se obsah procházeného kontejneru v průběhu procházení měnil: hlavně proto, aby bylo možné spolehlivě využívat tohoto příkazu z více zároveň běžících vláken nad tímtéž kontejnerem.

Rozšíření podpory pro nové třídy

Sestavujeme-li sami nějakou třídu, jejíž instance slouží jako kontejnery, je vhodné, abychom podporu implementovali. Příkaz for/in nevyužívá automaticky existenci enumerátoru (což je trochu škoda; bylo by snadné jej rozšířit tak, aby to dělal); musíme proto implementovat novou metodu, countByEnumeratingWithState:objects:count:. Příkaz for/in objektu, který má procházet, posílá právě tuto zprávu a hodnoty do řídící proměnné dosazuje na základě jejích výsledků.

Zpráva je navržena tak, aby mohla potenciálně zajistit vyšší efektivitu než NSEnumerator (který je sám efektivnější nežli procházení pole podle indexů); v praxi je to většinou nepodstatné, ale v případě procházení rozsáhlých datových objektů se to může stát významným rozdílem.

Metoda je deklarována takto:

-(NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState*)state
  objects:(id*)stackbuf count:(NSUInteger)len;

(Nový typ NSUInteger souvisí se čtyřiašedesátibitovým režimem práce a více si o něm řekneme později. Prozatím jej můžeme bez újmy na obecnosti považovat prostě za unsigned.)

V typickém případě využijeme pouze prvý argument a návratovou hodnotu. Struktura NSFastEnumerationState obsahuje následující pole:

  • unsigned long state: stav procházení. Metoda může měnit libovolně jeho hodnotu; jediné, co je dáno v API, je, že hodnota 0 znamená začátek cyklu;
  • unsigned long extra[5]: pomocné proměnné pro uložení stavu, pokud by proměnná state nestačila;
  • unsigned long *mutationsPtr: "hlídač" změny obsahu. Poprvé (tedy ve chvíli, kdy je state roven nule) jej nastavíme na libovolný údaj, který reflektuje obsah kontejneru. Pokud se obsah našeho kontejneru změnil, změní se i tato hodnota – a příkaz cyklu ohlásí chybu;
  • id *itemsPtr: odkaz na prvky, jež příkaz for/in prochází.

Metoda prostě nastaví ukazatel itemsPtr na následující prvky a vrátí jejich počet. Pokud prvky sama nemá uložena v podobě, v níž by na ně mohla rozumně odkázat, využije argumenty stackbuf a count: první je buffer, do nějž prvky může uložit, a druhý jeho velikost.

Ukažme si konkrétní (nijak zvlášť efektivní) implementaci takového procházení indexy, uloženými ve třídě NSIndexSet:

@interface NSIndexSet (FastEnumeration)
-(NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState*)state
  objects:(id*)stackbuf count:(NSUInteger)len;
@end
@implementation NSIndexSet (FastEnumeration)
-(NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState*)state
  objects:(id*)stackbuf count:(NSUInteger)len {
  if (!state->state) { // začínáme s cyklem
    state->state=[self firstIndex]+1; // nesmí to být 0!
    state->mutationsPtr=(void*)self; // NSIndexSet se nemění
  } else
    state->state=[self indexGreaterThanIndex:state->state-1]+1;
  if (state->state==NSNotFound) // konec, prošli jsme vše
    return 0;
  // připravíme příští hodnotu do "stackbuf". Mohlo by jich...
  // ... být až "len", avšak méně práce je dát tam jedinou :)
  stackbuf[0]=[NSNumber numberWithUnsignedInt:state->state-1];
  state->itemsPtr=stackbuf; // ... uvedeme odkaz na ni ...
  return 1; // ... a informujeme volajícího, že hodnota je jedna.
}

Je-li takováto kategorie k dispozici, můžeme používat příkaz for/in i nad instancemi třídy NSIndexSet, asi takto:

NSIndexSet *is=....;
for (NSNumber *n in is) ...

Obsah seriálu (více o seriálu):

Tématické zařazení:

 » Rubriky  » Informace  

 » Rubriky  » Agregator  

 » Rubriky  » Začínáme s  

 » Rubriky  » Software  

 

 

 

Nejčtenější články
Nejlépe hodnocené články
Apple kurzy

 

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

Uživatelské jméno:

Heslo: