Irbis: že prý žádné novinky? - 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ů



Software

Irbis: že prý žádné novinky?

29. září 2009, 00.00 | Více než po roce se opět vracíme se seriálem o programování v Kakau – je zde nová kočka s novými možnostmi, a na ty se v následující sérii článků podíváme blíže.

Pro uživatele byl Irbis inserován jako „verse, jež nebude obsahovat žádné novinky“ (ale jen opravy chyb). Potěšující je, že z hlediska programátora tomu tak není – alespoň tedy co se týká novinek: Irbis je novými a velmi zajímavými technologiemi i drobnými, ale cennými vylepšeními existujících API přímo nabit.

(Zbývá jen doufat, že nebude také nabit novými chybami; po zkušenostech s Le Prdem mám poněkud obavy, ale klepu na dřevo a doufám. Mimochodem, závažná chyba, již jsme si ukázali v minulém článku, je již opravena; méně závažná chybka varování nikoli, ale to v zásadě asi moc nevadí, neboť překladač GNU C v Mac OS X stejně pěje píseň labutí – k tomu se ale vrátíme až později.)

Podíváme se na novinky a rozšíření samotného jazyka Objective C, na nové technologie a jim odpovídající knihovny, ukážeme si novinky ve Foundation i v Application Kitu, a pak… uvidíme: bude-li zájem, můžeme opět spustit pravidelný seriál o programování; témat je k dispozici dost a dost :)

Bloky? Bloky. Bloky!

Hned dnes se podíváme, co je nového v samotném jazyce. Moc toho není; v podstatě jde o jedinou změnu – ale o změnu velmi podstatnou a důležitou; osobně bych si dovolil ji označit za vůbec nejvýznamnější doplněk jazyka od doby, kdy Brad Cox implementoval objc_send: při programování v Mac OS X totiž nyní můžeme používat bloky.

Co je to blok

V kontextu klasických strukturovaných programovacích jazyků se občas pojem blok používal pro složený příkaz, který mohl obsahovat deklarace vlastních proměnných – třeba nějak takto:

void fnc(NSUInteger n) {
  for (NSUInteger i=0;i<n;i++) {
    printf("Toto se provede %lu-krát.\n",n);
  }

}

„Opravdický blok“ – v angličtině se někdy pro odlišení užívá pojmu closure, který se nakolik je mi známo nedá nijak rozumně přeložit do češtiny, takže se budeme držet českého termínu „blok“ – je v podstatě totéž; jen se to může vzít jako samostatný celek a z postavení ve funkci „vyndat“ a použít jindy a jinde. Udělat ze složeného příkazu skutečný blok, je triviální – prostě před jeho otevírací složenou závorku přidáme znak ‚^‘. Blok můžeme třeba uložit do proměnné  – blokové proměnné se deklarují přesně stejně, jako ukazatele na funkce, jen namísto ‚*‘ mají ‚^‘ – a jejím prostřednictvím zavolat, nějak takto:

  ...
  void (^block)()=^{ // teď již je to opravdický blok!
    printf("Toto se provede %d-krát.\n“,n);
  }
  for (i=a;i<b;i++) block();
  ...

Výše uvedený příklad ovšem nepřinesl nic zajímavého – jen ilustroval princip, na němž bloky fungují. Opravdu zajímavé to začne být ve chvíli, kdy bloky použijeme jako argumenty funkcí (či zpráv Objective C, ale to si ukážeme až za chvilku). Volající pak nebude samozřejmě ukládat blok do proměnné, ale přímo jej zapíše na místo argumentu volané funkce – nějak takto:

  ...
  atexit_b(^{
    printf("Program je ukončen, uklízíme...\n");
    ...
  });
  ...

V tomto případě se zadaný blok – ať už je jeho obsahem cokoli – automaticky provede bezprostředně před ukončením programu.

Bloky a proměnné

Bloky mohou mít – přesně stejně jako funkce – argumenty (a také návratovou hodnotu). Blok, použitý v cyklu, by tak nejspíše měl jeden argument, jímž by byla hodnota řídící proměnné, nějak takto:

void fnc(NSUInteger count) {
  void (^block)(NSUInteger)=^(NSUInteger n){
    printf("%lu-tý průchod cyklem\n",n);
  };
  for (NSUInteger i=0;i<count;i++) block(i);
}

Ještě zajímavější je to, že bloky mají přístup ke všem proměnným, jež jsou dostupné ve chvíli jejich deklarace, a „zafixují“ si jejich hodnoty. Podívejme se na jednoduchoučkou modifikaci minulé ukázky, kdy blok nevytvoříme uvnitř funkce, ale předáme jí jej jako argument:

void fnc(NSUInteger count,void(^block)(NSUInteger)) {
  for (NSUInteger i=0;i<count;i++) block(i);
}

a na následující příklad programu, který jí využije pro několikanásobný opis každého druhého ze svých vstupních argumentů (přičemž prvé argumenty určují počet opakování):

int main(int argc,char *argv[]) {
  for (int an=1;an<argc;an+=2)
    fnc(atoi(argv[an]),^(int n){printf("%d: %s\n",n+1,argv[an+1]);});
  return 0;
}

~> cc -std=gnu99 q.m && ./a.out 2 dva 3 tri
1: dva
2: dva
1: tri
2: tri
3: tri
~> 

Princip funkce je zřejmý – v cyklu procházíme vstupní argumenty; každý prvý z nich použijeme (po převodu na číslo pomocí standardní knihovní funkce atoi) jako počet opakování bloku. Uvnitř bloku pak vypíšeme pořadové číslo – jež blok dostane jako vstupní argument – a následující argument příkazového řádku.

A zde je vhodné se na chvilku zarazit: funguje to logicky tak, jak bychom čekali, vypisují se ty správné argumenty... ale jak se k nim blok, volaný z funkce fnc, dostane, když v ní nikde nejsou k dispozici ani seznam argumentů argv, ani pomocná proměnná an cyklu z funkce main?

To je právě ona schopnost bloků, o níž hovoříme: ve chvíli, kdy je blok definován, si zafixuje hodnoty všech proměnných, jež jsou v něm použity. Jinde, kde je volán (a kde tyto proměnné obecně nejsou přístupné), je pak blok může bez problémů použít. Jinde – nebo klidně i jindy! Prostudujte si následující příklad, a uvědomte si, co z něj pro široké možnosti práce s bloky vyplývá:

int main(int argc,char *argv[]) {
  for (int an=1;an<argc;an+=2) {
    int count=atoi(argv[an]);
    if (count) fnc(count,^(int n){printf("%d: %s\n",n+1,argv[an+1]);});
    else atexit_b(^{printf("argument #%d (%s) \n",an,argv[an+1]);});
  }
  printf("Konec funkce main.\n"); // nyní již proměnná 'an' neexistuje
  return 0;
} // a nyní již neexistují ani argumenty 'argc' a 'argv'
~> cc -std=gnu99 q.m && ./a.out 0 konec 1 cosi
1: cosi
Konec funkce main.
argument #1 (konec) 
~> 

Podpora bloků v Mac OS X

Jednu ze služeb, jež využívá bloků, jsme si již ukázali – jde o novou standardní službu atexit_b, jež blok uloží a provede jej těsně před ukončením programu. API Irbisu podobných služeb nabízí mnoho, řadu na úrovni plain C, a další v objektových knihovnách Cocoa. Bezmála kdekoli, kde dává smysl „provést nějaký kus kódu jinde, jindy nebo jinak než právě teď“, můžeme pro daný účel velmi pohodlně využít právě bloků.

Snad nejvýraznějším příkladem je malý zázrak jménem Grand Central Dispatch – technologie, jež nejenže umožňuje psát efektivní paralelní kód o mnoho řádů snáze, než kdybychom implementovali vlákna pomocí služeb vhodné knihovny (např. NSThread v Cocoa); navíc jsou takto napsané programy daleko efektivnější. My se na ni podíváme později v samostatném článku podrobněji; vyplatí se ale ukázat si hned to, jak využívá bloků.

Nejjednodušším příkladem může být tento kód, který simuluje čtveřici časově náročných operací:

int main(int argc,char *argv[]) {
  for (NSUInteger i=0;i<4;i++) {
    printf("operace %lu...\n",i+1);
    sleep(2);
    printf("... operace %lu hotova\n",i+1);
  }
  return 0;
}
~> cc -std=gnu99 q.m && time ./a.out
operace 1...
... operace 1 hotova
operace 2...
... operace 2 hotova
operace 3...
... operace 3 hotova
operace 4...
... operace 4 hotova
./a.out  0.00s user 0.00s system 0% cpu 8.010 total
~> 

Ten, kdo má nějaké zkušenosti s programováním s vlákny, ví, že převést tento triviální kód na paralelní běh by nebylo příliš těžké, ale rozhodně by bylo zapotřebí počet programových řádků minimálně zdvojnásobit. Díky blokům (a technologii Grand Central Dispatch) však stačí jen nahradit složený příkaz v těle cyklu blokem (tj. vlastně jen přidat ‚^‘ a řídící proměnnou), a nahradit příkaz for voláním vhodné funkce GCD:

int main(int argc,char *argv[]) {
  dispatch_apply(4,dispatch_get_global_queue(0,0),^(size_t i){
    printf("operace %lu...\n",i+1);
    sleep(2);
    printf("... operace %lu hotova\n",i+1);
  });
  return 0;
}
~> cc -std=gnu99 q.m && time ./a.out
operace 1...
operace 2...
... operace 1 hotova
operace 3...
... operace 2 hotova
operace 4...
... operace 3 hotova
... operace 4 hotova
./a.out  0.00s user 0.00s system 0% cpu 4.004 total
~> 

(Mimochodem, proč se neprovedly všechny čtyři operace naráz, ale běžely po dvojicích? Proto, že techologie GCD dobře ví, že na počítači jsou právě dvě jádra, a nemá tedy smysl spouštět více než dvě vlákna /jež neužívají semaforů/ najednou.)

Ukažme si na závěr jen jednu z mnoha – opravdu z mnoha! – možností využití bloků v Objective C. Dejme tomu, že máme třeba pole objektů, representujících běžící aplikace, a že je chceme setřídit nějakým velmi absurdním způsobem, jenž by bylo obtížné nebo nemožné formulovat standardním způsobem s využitím objektů NSSortDescriptor – třeba chceme třídit podle třetího písmene názvu. Tradičně bychom samozřejmě mohli použít službu sortedArrayUsingFunction:; to ovšem znamená definovat statickou funkci na jiném místě, než kde třídíme – a to je nešikovné, nepohodlné a přináší to risiko chyb při údržbě a změnách kódu.

S bloky není nic snazšího; vypadalo by to – včetně ochrany pro případ, že některý z názvů není dostatečně dlouhý – asi nějak takto:

NSArray *sorted=[[[NSWorkspace sharedWorkspace] runningApplications] sortedArrayUsingComparator:
  ^NSComparisonResult(id a,id b){
    if ([a=[a localizedName] length]<3 || [b=[b localizedName] length]<3) return NSOrderedSame;
    return [a characterAtIndex:2]-[b characterAtIndex:2];
  }];

Za povšimnutí stojí kompletní deklarace bloku včetně typu jeho návratové hodnoty („^NSComparisonResult(id a,id b)“) – v takovýchto případech to je zapotřebí, neboť překladač nemůže s jistotou návratovou hodnotu odhadnout z kódu, a výsledkem může být chyba.

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

Tématické zařazení:

 » Rubriky  » Informace  

 » Rubriky  » Agregator  

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