Nastal čas na kakao - NSInvocation a černá magie - 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

 

Odkud pochází fotografka Anne Erhard?

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

Seriály

Více seriálů



Začínáme s

Nastal čas na kakao - NSInvocation a černá magie

19. dubna 2005, 00.00 | Ačkoli v praxi využívaná poměrně zřídkakdy, stojí třída NSInvocation za samostatný článek, protože jejím prostřednictvím máme v API Cocoa k dispozici nesmírně silný nástroj – přesměrování zpráv. Možnosti, jež nám tato služba nabízí, si programátoři ve statických prostředích typu C++ obvykle vůbec nedokáží představit.

Ačkoli v praxi využívaná poměrně zřídkakdy, stojí třída NSInvocation za samostatný článek, protože jejím prostřednictvím máme v API Cocoa k dispozici nesmírně silný nástroj – přesměrování zpráv. Možnosti, jež nám tato služba nabízí, si programátoři ve statických prostředích typu C++ obvykle vůbec nedokáží představit. My jsme se již s touto službou setkali: připomeňme si díl věnovaný vkládání objektů, v němž je i ukázka konkrétního použití třídy NSInvocation.

Základní využití NSInvocation

Připomeňme si, jak přesměrování zpráv v Objective C funguje: už jsme si postupně v několika různých dílech našeho seriálu řekli hodně o tom, jak systém runtime při předávání zprávy objektu postupuje; víceméně kompletní popis (abstrahující jen pro zjednodušení od faktu, že zprávy lze posílat instancím i třídám) vypadá takto:

  • nejprve runtime nalezne třídu, jejíž instance je příjemcem zprávy;
  • v implementaci této třídy (a všech jejích případných kategoriích) pak hledá metodu, jejíž jméno odpovídá jménu zprávy. Pokud ji nalezne, prostě ji zavolá;
  • pokud ji nenalezne, hledá znovu týmž způsobem v nadtřídě (a jejích kategoriích), případně v její nadtřídě (a...), a tak dále. Nalezne-li odpovídající metody, prostě ji zavolá;
  • pokud metodu nenalezne ani v kořenové třídě, pokusí se využít dynamického zpracování zpráv: nejprve zjistí počet a typy argumentů zprávy a typ návratové hodnoty. To runtime udělá tak, že objektu pošle standardní zprávu methodSignatureForSelector:, jejímž argumentem je selektor zprávy původní. Stojí za to zdůraznit, že i pro předávání této poněkud speciální zprávy slouží standardní mechanismus, popsaný v předcházejících bodech, takže odpovídající metoda může být implementována stejně dobře přímo v cílové třídě jako v její nadtřídě či v některé z jejich kategorií;
  • nepodaří-li se pro selektor získat signaturu, skončí program běhovou chybou "pokus o zaslání neznámé zprávy X objektu třídy Y";
  • pokud runtime signaturu získá, zkonstruuje na jejím základě a na základě skutečných argumentů, předaných při volání zprávy, instanci třídy NSInvocation representující původní zprávu (kód, který se k tomu využije, se koncepčně podobá kódu z posledního příkladu v tomto článku, je však samozřejmě mnohem obecnější). Tato instance se pak předá objektu prostřednictvím standardní zprávy forwardInvocation:. I ta je předávána standardním postupem, takže i ona může být implementována v některé z nadtříd či jejich kategorií.

Objekt pak v rámci vlastní implementace metody forwardInvocation: může zajistit zcela obecné zpracování zprávy – může třeba pro každou zprávu bez argumentu, jejíž jméno začíná písmenem 'a', vrátit nulu, a pro všechny ostatní zprávy může vracet počet znaků v jejich jménech. Ukažme si pro lepší představu implementaci takové dynamické prapodivnosti:

@interface Weird:NSObject @end
@implementation Weird
-(int)dummyMethodWithoutAnyArgument {return 0;}
-(NSMethodSignature*)methodSignatureForSelector:(SEL)sel {
  NSMethodSignature *sig=[super methodSignatureForSelector:sel];
  if (sig) return sig; // standardní metody děděné od NSObjectu
  return [self methodSignatureForSelector:@selector(dummyMethodWithoutAnyArgument)];
}
-(void)forwardInvocation:(NSInvocation*)inv {
  NSString *messageName=NSStringFromSelector([inv selector]);
  int returnValue=0; // covers the 'a...' case
  if (![messageName hasPrefix:@"a"]) returnValue=[messageName length];
  [inv setReturnValue:&returnValue];
}

@end

Volat bychom ji mohli třeba takto:

int main() {
  [NSAutoreleasePool new];
  id weird=[Weird new];
  // samozřejmě následující řádky budou hlásit warningy
  // budou však korektně fungovat
  NSLog(@"ahoj: %d",[weird ahoj]);
  NSLog(@"nazdar: %d",[weird nazdar]);
  NSLog(@"x: %d",[weird x]);
  NSLog(@"hola_hola_hou: %d",[weird hola_hola_hou]);
  return 0;
}

Samozřejmě, jak už jsme si ukázali v článku, na nějž vede minulý odkaz, ještě jednodušší je prostě zprávu přesměrovat jinému objektu – k tomu slouží zpráva invoke (resp. invokeWithTarget:) třídy NSInvocation, a konkrétní příklad byl v článku uveden.

Pro pokročilé: kompletní sestavení dynamické zprávy

Třída NSInvocation toho ovšem umí mnohem, mnohem více – díky ní můžeme třeba za běhu dynamicky zkonstruovat objekt, reprezentující předání libovolné zprávy s libovolnými parametry libovolnému jinému objektu, a tuto akci pak kdykoli realizovat. Ukažme si zde takový triviální příklad:

NSMutableDictionary *dict=...;
NSString *a=...,*b=...;
// selektor požadované zprávy:
SEL sel=@selector(setObject:forKey:);
// signatura (popis argumentů) požadované zprávy:
NSMethodSignature *sig=[NSMutableDictionary instanceMethodSignatureForSelector:sel];
// odpovídající objekt NSInvocation:
NSInvocation *inv=[NSInvocation invocationWithMethodSignature:sig];
// základní atributy – cílový objekt, zpráva:
[inv setSelector:sel];
[inv setTarget:dict];
// nastavíme parametry – objekty a a b:
[inv setArgument:&a atIndex:2];
[inv setArgument:&b atIndex:3];
// odešleme zprávu:
[inv invoke];
// pokud by zpráva vrátila nějakou hodnotu,
// získali bychom ji také snadno:
// [inv getReturnValue:...];

Další možnosti využití třídy NSInvocation a jejích služeb jsou plně dynamické objektové služby systémů typu HOM (High Order Messaging) – jde o velmi pokročilou technologii, jež namísto

NSEnumerator *en=[pole objectEnumerator];
id o;
while (o=[en nextObject]) [o zcelaObecnaZprava];

dovoluje prostě a jednoduše napsat něco jako

[[pole each] zcelaObecnaZprava];

a mnohem více – my se v našem seriálu samozřejmě seznámíme i s takovýmito možnostmi, ale až později, neboť jde skutečně o služby "pro starší a pokročilé".

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: