Sledování změn objektů: přímý přístup - 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ů



Software

Sledování změn objektů: přímý přístup

4. dubna 2006, 00.00 | Co dělat v případě, kdy z jakýchkoli důvodů potřebujeme přímo měnit obsah některé z proměnných, tedy kdy nechceme nebo nemůžeme použít set-accessor? Ve složitějších případech, nebo tehdy, když potřebujeme dosáhnout co největší efektivity, můžeme také automatické posílání notifikací KVO zcela vypnout.

Připomeňme, že minule jsme si ukázali, jak správně implementovat modelové třídy – většinou se nemusíme starat zhola o nic, někdy musíme jen ještě implementovat třídní metodu initialize, jež pomocí standardní zprávy setKeys:triggerChangeNotificationsForDependentKey: nastaví správně závislosti odvozených atributů na ostatních.

Hlášení změn proměnných

Jednu drobnost jsme ale nechali otevřenou: co dělat v případě, kdy z jakýchkoli důvodů potřebujeme přímo měnit obsah některé z proměnných, tedy kdy nechceme nebo nemůžeme použít set-accessor? Typickým případem může být situace, kdy dva accessory oba mění tytéž proměnné – pokud bychom tedy v obou volali pro nastavení accessor, došlo by k zacyklení.

Nejjednodušším příkladem může být třeba třída obsahující atributy width a height, jež spolu mají zůstávat v poměru, odpovídajícím poměru stran televizní obrazovky – pokud by nám nezáleželo na podpoře KVO, mohli bychom accessory naprogramovat třeba takto:

@interface Test:NSObject {
    float width,height;
}
@end
@implementation Test
-(void)setWidth:(float)w {
    height=(width=w)*3./4.;
}
-(void)setHeight:(float)h {
    width=(height=h)*4./3.;
}
@end

Pro podporu KVO musíme nějak "říci" systému, že měníme hodnotu některé z proměnných objektu. API KVO k tomu nabízí standardní zprávy willChangeValueForKey: a didChangeValueForKey:. Prvou z nich musíme odeslat předtím, než k jakékoli změně dojde (systém KVO si v ní totiž ukládá "starou" hodnotu proměnné, již mohou potřebovat někteří z příjemců notifikací KVO); druhou pak pošleme ve chvíli, kdy jsou změny ukončeny – a systém KVO v jejím rámci rovnou pošle odpovídající notifikaci:

-(void)setWidth:(float)w {
    [self willChangeValueForKey:@"height"];
    height=(width=w)*3./4.;
    [self didChangeValueForKey:@"height"];
}
-(void)setHeight:(float)h {
    [self willChangeValueForKey:@"width"];
    width=(height=h)*4./3.;
    [self didChangeValueForKey:@"width"];
}

Efektivnější, než nastavení na úrovni třídy

Pozorní čtenáři minulého dílu ovšem mohou namítnout, že namísto uvedeného kódu bychom se stejně dobře obešli s použitím KVO nepodporujících accessorů z prvého příkladu, pokud bychom ve třídě ještě navíc použili kód

+(void)initialize {
  [self setKeys:[NSArray arrayWithObject:@"width"]
    triggerChangeNotificationsForDependentKey:@"height"];
  [self setKeys:[NSArray arrayWithObject:@"height"]
    triggerChangeNotificationsForDependentKey:@"width"];
}

– a bylo by to možná i méně práce. To je pravda – v našem triviálním příkladu; v praxi, kdy různé metody mohou měnit různé proměnné, nadto ještě pouze při splnění různých podmínek, však je přímé použití zpráv willChangeValueForKey: a didChangeValueForKey: pohodlnější a také daleko efektivnější, neboť je můžeme volat také podmíněně, kdežto notifikace na základě nastavení zprávou setKeys:triggerChangeNotificationsForDependentKey: se posílají vždy:

-(void)setFoo:foo {
  ...
  if (...) {
    [self willChangeValueForKey:@"a"];
    a=...;
    [self didChangeValueForKey:@"a"];
    if (...) {
      [self willChangeValueForKey:@"b"];
      [self willChangeValueForKey:@"c"];
      [self willChangeValueForKey:@"d"];
      b=...;
      c=...;
      d=...;
      [self didChangeValueForKey:@"d"];
      [self didChangeValueForKey:@"c"];
      [self didChangeValueForKey:@"b"];
    }
  }
  ...
}

Mimochodem, povšimněte si pořadí zpráv ve druhém příkazu if – to je důležité: pokud "otevřeme" změnu více atributů najednou pomocí několika zpráv willChangeValueForKey:, musíme ji "uzavřít" odpovídajícími zprávami didChangeValueForKey: v přesně opačném pořadí (jinak by za určitých okolností mohlo dojít k deadlocku).

Speciální zprávy pro relace 1:N

Podobně, jako systém KVC umožňoval dosáhnout vyšší efektivity využitím speciálních zpráv pro relace 1:N, zvláštní zprávy pro tento případ nabízí i KVO – to proto, že pokud řekneme pouze "pole se mění", je to příliš hrubá informace, a automatické zjišťování toho jak se změnilo je poměrně problematické a neefektivní. Namísto toho tedy nabízí KVO speciální zprávy ...will- a ...didChange, jejichž prostřednictvím můžeme přímo určit typ změny, kterou provádíme.

Pro setříděné relace 1:N, které jsou obvykle (ale jak víme z KVC, samozřejmě ne nutně) representovány objektem třídy NSArray, jsou určeny zprávy

-willChange:change valuesAtIndexes:indexes forKey:key;
-didChange:change valuesAtIndexes:indexes forKey:key;

kde argument change určuje typ změny a může nabývat hodnot NSKeyValueChangeInsertion, NSKeyValueChangeRemoval či NSKeyValueChangeReplacement pro ohlášení vložení nových objektů, odstranění existujících nebo jejich změnu. Objekty jsou identifikovány prostřednictvím seznamu indexů indexes, uloženého v objektu třídy NSIndexSet.

Podobně pak pro nesetříděné relace 1:N, typicky (ale ne nutně) representované objekty třídy NSSet, jsou k dispozici standardní zprávy

-didChangeValueForKey:key withSetMutation:kind usingObjects:objects;
-didChangeValueForKey:key withSetMutation:kind usingObjects:objects;

Zde pak argument objects typu NSSet obsahuje přímo měněné objekty, kdežto výčtová hodnota kind říká, k jaké změně dochází: NSKeyValueUnionSetMutation informuje o vložení nových objektů, NSKeyValueMinusSetMutation naopak o odebrání objektů existujících; podobně slouží i NSKeyValueIntersectSetMutation, kdy je jediný rozdíl v tom, že argument objects neobsahuje objekty odebírané, nýbrž naopak ty, jež zůstávají. Konečně pak NSKeyValueSetSetMutation indikuje, že se obsah zcela mění.

Automatiku můžeme také vypnout

Ve složitějších případech, nebo tehdy, když potřebujeme dosáhnout co největší efektivity, můžeme také automatické posílání notifikací KVO zcela vypnout – stačí implementovat třídní metodu automaticallyNotifiesObserversForKey: , jejímž prostřednictvím systém KVO zjišťuje, pro které atributy má automaticky notifikace posílat. V rámci její implementace pak prostě vrátíme hodnotu NO pro atributy, pro něž automatické odesílání notifikací nechceme – vypnutí pro atributy width a height z úvodního příkladu by tedy mohlo vypadat například takto:

+(BOOL)automaticallyNotifiesObserversForKey:(NSString*)key {
  if ([key isEqualToString:@"width"]) return NO;
  if ([key isEqualToString:@"height"]) return NO;
  return [super automaticallyNotifiesObserversForKey:key];
}

Samozřejmě pak musíme všechny notifikace odesílat explicitně – opět pomocí týchž zpráv willChangeValueForKey: a didChangeValueForKey: (respektive jejich speciálních variant pro relace 1:N), s nimiž jsme se seznámili v minulém odstavci – tentokrát je musíme poslat i pro tu proměnnou, jíž accessor patří:

-(void)setWidth:(float)w {
    [self willChangeValueForKey:@"width"];
    [self willChangeValueForKey:@"height"];
    height=(width=w)*3./4.;
    [self didChangeValueForKey:@"height"];
    [self didChangeValueForKey:@"width"];
}
-(void)setHeight:(float)h {
    [self willChangeValueForKey:@"width"];
    [self willChangeValueForKey:@"height"];
    width=(height=h)*4./3.;
    [self didChangeValueForKey:@"height"];
    [self didChangeValueForKey:@"width"];
}

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

Tématické zařazení:

 » Rubriky  » Informace  

 » Rubriky  » Agregator  

 » Rubriky  » Software  

 

 

 

 

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

Uživatelské jméno:

Heslo: