44 votes

les meilleures pratiques pour le paramètre de contexte dans addObserver (KVO)

Je me demandais ce que vous devez définir le Contexte pointeur dans KVO lorsque vous observez une propriété. Je viens juste de commencer à utiliser KVO et je n'ai pas glanés trop de la documentation. Je vois sur cette page: http://www.jakeri.net/2009/12/custom-callout-bubble-in-mkmapview-final-solution/ l'auteur fait ceci:

[annView addObserver:self
forKeyPath:@"selected"
options:NSKeyValueObservingOptionNew
context:GMAP_ANNOTATION_SELECTED];

Et puis, dans le rappel, fait ceci:

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context{

NSString *action = (NSString*)context;


if([action isEqualToString:GMAP_ANNOTATION_SELECTED]){

Je suppose que dans ce scénario, l'auteur crée une chaîne de caractères qui sera identifié plus tard dans le rappel.

Puis dans iOS 5 en Poussant les Limites du livre, je vois qu'il fait ceci:

[self.target addObserf:self forKeyPath:self.property options:0 context:(__bridge void *)self];

rappel:

if ((__bridge id)context == self) {
}
else {
   [super observeValueForKeyPath .......];
}

Je me demandais si il existe une norme ou de meilleures pratiques pour passer dans le contexte de pointeur? Merci!

102voto

ipmcc Points 16609

L'important, c'est qu'en général, vous utilisez quelque chose (plutôt que rien) et que tout ce que vous utilisez, être unique et privé pour votre usage.

Le principal écueil ici se passe quand vous avez une observation dans une de vos classes, et puis quelqu'un de votre sous-classes de la classe, et ils ajoutent une autre observation d'un même objet observé et le même chemin d'accès clé. Si votre originale observeValueForKeyPath:... seulement la mise en œuvre vérifié keyPath, ou observées object, ou même les deux, qui pourrait ne pas être suffisante pour savoir que c'est votre observation appelé revenir. À l'aide d'un context dont la valeur est unique et privée vous permet d'être beaucoup plus sûr que d'un appel à l' observeValueForKeyPath:... est l'appel que vous attendez qu'elle soit.

Ce serait grave si, par exemple, vous avez enregistré uniquement pour didChange des notifications, mais une sous-classe de registres pour le même objet et le chemin d'accès clé avec l' NSKeyValueObservingOptionPrior option. Si vous n'avez pas de filtrage des appels d' observeValueForKeyPath:... à l'aide d'un context (ou pour vérifier le changement de dictionnaire), votre gestionnaire d'exécuter plusieurs fois, lorsque vous ne l'attendait à exécuter une fois. Il n'est pas difficile d'imaginer comment cela pourrait causer des problèmes.

Le modèle que j'utilise est:

static void * const MyClassKVOContext = (void*)&MyClassKVOContext;

Ce pointeur pointe vers son propre site, et que l'emplacement est unique (pas d'autres statique ou variable globale peut avoir cette adresse, ni aucune de segment de pile ou objet alloué jamais avoir cette adresse -- c'est assez forte, mais certes pas absolue, garantie), grâce à l'éditeur de liens. L' const fait en sorte que le compilateur va nous avertir si nous avons jamais essayé d'écrire du code qui permettrait de modifier la valeur du pointeur, et enfin, static fait privé de ce fichier, de sorte que personne en dehors de ce fichier peut obtenir une référence à elle (encore une fois, ce qui rend plus probable pour éviter les collisions).

Un modèle que je voudrais spécialement attention à l'encontre de l'aide est celui qui est apparu dans la question:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    NSString *action = (NSString*)context;
    if([action isEqualToString:GMAP_ANNOTATION_SELECTED]) {

context est déclarée void*, ce qui signifie que c'est la garantie qui peut être faite à propos de ce qu'il est. Par moulage d'un NSString* vous êtes à l'ouverture d'une grosse boîte de potentiel de méchanceté. Si quelqu'un arrive à avoir un enregistrement qui n'est pas utiliser un NSString* de la context paramètre, cette approche se bloquer lorsque vous passez la non-valeur de l'objet d' isEqualToString:. Pointeur de l'égalité (ou alternativement intptr_t ou uintptr_t de l'égalité) sont la seule sécurité des chèques qui peuvent être utilisés avec un context de la valeur.

À l'aide de self comme context est une approche commune. C'est mieux que rien, mais elle est beaucoup plus faible uniquing et de la vie privée, puisque d'autres objets (pour ne pas mentionner les sous-classes) ont accès à la valeur de self et peut l'utiliser comme un context (cause de l'ambiguïté), contrairement à l'approche que j'ai proposé ci-dessus.

Aussi rappelez-vous, ce n'est pas juste les sous-classes qui pourraient causer des pièges ici; Même si c'est sans doute l'un des rares motif, il n'y a rien que le fait de prévenir un autre objet de l'enregistrement de votre objet pour les nouveaux KVO observations.

Pour une meilleure lisibilité, vous pouvez également envelopper ceci dans une macro préprocesseur comme:

#define MyKVOContext(A) static void * const A = (void*)&A;

19voto

Nate Chandler Points 3020

Le KVO contexte doit être un pointeur vers une variable statique, comme ce résumé montre. Généralement, je me retrouve à faire ce qui suit:

Près du haut de mon fichier ClassName.m je vais avoir la ligne

static char ClassNameKVOContext = 0;

Quand je commence l'observation de l' aspect bien sur targetObject (une instance d' TargetClass) je vais avoir

[targetObject addObserver:self
               forKeyPath:PFXKeyTargetClassAspect
                  options://...
                  context:&ClassNameKVOContext];

où PFXKeyTargetClassAspect est un NSString * définie en TargetClass.m être égale à @"aspect" et déclarée extern en TargetClass.h. (Bien sûr PFX est juste un espace réservé pour le préfixe que vous utilisez dans votre projet). Cela me donne l'avantage de la saisie semi-automatique et me protège de fautes de frappe.

Quand j'ai fini d'observation aspect sur targetObject je vais avoir

[targetObject removeObserver:self
                  forKeyPath:PFXKeyTargetClassAspect
                     context:&ClassNameKVOContext];

Afin d'éviter de trop en retrait dans mon implémentation -observeValueForKeyPath:ofObject:change:context:,, j'aime écrire

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context != &ClassNameKVOContext) {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        return;
    }

    if ([object isEqual:targetObject]) {
        if ([keyPath isEqualToString:PFXKeyTargetClassAspect]) {
            //targetObject has changed the value for the key @"aspect".
            //do something about it
        }
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X