web-dev-qa-db-fra.com

Observer un NSMutableArray pour l'insertion/suppression

Une classe a une propriété (et une instance var) de type NSMutableArray avec des accesseurs synthétisés (via @property). Si vous observez ce tableau en utilisant:

[myObj addObserver:self forKeyPath:@"theArray" options:0 context:NULL];

Et puis insérez un objet dans le tableau comme ceci:

[myObj.theArray addObject:NSString.string];

Une notification observeValueForKeyPath ... est pas envoyé. Toutefois, les éléments suivants envoient la notification appropriée:

[[myObj mutableArrayValueForKey:@"theArray"] addObject:NSString.string];

Ceci parce que mutableArrayValueForKey renvoie un objet proxy qui se charge de notifier les observateurs.

Mais les accesseurs synthétisés ne devraient-ils pas automatiquement renvoyer un tel objet proxy? Quelle est la bonne façon de contourner ce problème - devrais-je écrire un accesseur personnalisé qui appelle simplement [super mutableArrayValueForKey...]?

73
Adam Ernst

Mais les accesseurs synthétisés ne devraient-ils pas automatiquement renvoyer un tel objet proxy?

Non.

Quelle est la bonne façon de contourner ce problème - devrais-je écrire un accesseur personnalisé qui appelle simplement [super mutableArrayValueForKey...]?

Implémentez les accesseurs array . Lorsque vous les appelez, KVO publiera automatiquement les notifications appropriées. Donc tout ce que vous avez à faire c'est:

[myObject insertObject:newObject inTheArrayAtIndex:[myObject countOfTheArray]];

et la bonne chose se produira automatiquement.

Pour plus de commodité, vous pouvez écrire un accesseur addTheArrayObject:. Cet accesseur appelle l'un des accesseurs réels de la matrice décrits ci-dessus:

- (void) addTheArrayObject:(NSObject *) newObject {
    [self insertObject:newObject inTheArrayAtIndex:[self countOfTheArray]];
}

(Vous pouvez et devez renseigner la classe appropriée pour les objets du tableau, à la place de NSObject.)

Ensuite, au lieu de [myObject insertObject:…], vous écrivez [myObject addTheArrayObject:newObject].

Malheureusement, add<Key>Object: et son homologue remove<Key>Object: sont, en dernier lieu, vérifiés, mais uniquement par KVO pour les propriétés set (comme dans NSSet), et non pour les propriétés de tableau. Vous ne recevez donc pas de notifications KVO gratuites, à moins que vous ne les implémentiez par dessus reconnaît. J'ai déposé un bogue à ce sujet: x-radar: // problem/6407437

J'ai une liste de tous les formats de sélecteurs d'accesseurs sur mon blog.

78
Peter Hosey

Je n'utiliserais pas willChangeValueForKey et didChangeValueForKey dans cette situation. D'une part, ils sont censés indiquer que la valeur de ce chemin a changé, mais pas que les valeurs d'une relation à plusieurs sont en train de changer. Vous voudriez utiliser willChange:valuesAtIndexes:forKey: à la place, si vous l'avez fait de cette façon. Néanmoins, utiliser des notifications KVO manuelles comme ceci constitue une mauvaise encapsulation. Une meilleure façon de le faire est de définir une méthode addSomeObject: dans la classe qui possède le tableau, ce qui inclurait les notifications manuelles KVO. De cette façon, les méthodes extérieures qui ajoutent des objets au tableau n'ont pas besoin de s'inquiéter de la gestion du KVO du propriétaire du tableau, ce qui ne serait pas très intuitif et pourrait conduire à un code inutile et éventuellement à des bugs si vous commencez à ajouter des objets au tableau de plusieurs endroits.

Dans cet exemple, je continuerais à utiliser mutableArrayValueForKey:. Je ne suis pas positif avec les tableaux mutables, mais je crois qu'en lisant la documentation, cette méthode remplace tout le tableau par un nouvel objet. Par conséquent, si les performances vous préoccupent, vous voudrez également implémenter insertObject:in<Key>AtIndex: et removeObjectFrom<Key>AtIndex: dans la classe propriétaire. le tableau.

9
Marc Charbonneau

lorsque vous souhaitez simplement observer le nombre de modifications, vous pouvez utiliser un chemin de clé agrégé:

[myObj addObserver:self forKeyPath:@"theArray.@count" options:0 context:NULL];

mais sachez que toute réorganisation dans le tableau ne sera pas déclenchée.

7
berbie

Votre propre réponse à votre propre question est presque exacte. Ne vendez pas theArray à l'extérieur. Déclarez plutôt une propriété différente, theMutableArray, correspondant à aucune variable d'instance, et écrivez cet accesseur:

- (NSMutableArray*) theMutableArray {
    return [self mutableArrayValueForKey:@"theArray"];
}

Le résultat est que d'autres objets peuvent utiliser thisObject.theMutableArray pour apporter des modifications au tableau, et ces modifications déclenchent KVO.

Les autres réponses soulignant que l'efficacité est accrue si vous implémentez également insertObject:inTheArrayAtIndex: et removeObjectFromTheArrayAtIndex: sont toujours correctes. Mais il n’est pas nécessaire que d’autres objets aient à connaître ces objets ou les appellent directement.

3
matt

Si vous n'avez pas besoin du setter, vous pouvez également utiliser la forme plus simple ci-dessous, qui offre des performances similaires (même taux de croissance dans mes tests) et moins passe-passe-passe.

// Interface
@property (nonatomic, strong, readonly) NSMutableArray *items;

// Implementation
@synthesize items = _items;

- (NSMutableArray *)items
{
    return [self mutableArrayValueForKey:@"items"];
}

// Somewhere else
[myObject.items insertObject:@"test"]; // Will result in KVO notifications for key "items"

Cela fonctionne parce que si les accesseurs de tableau ne sont pas implémentés et qu'il n'y a pas de programme de définition pour la clé, mutableArrayValueForKey: cherchera une variable d'instance nommée _<key> ou <key>. S'il en trouve un, le proxy transmettra tous les messages à cet objet.

Voir ces documents Apple , section "Modèle de recherche d'accesseur pour les collections classées", n ° 3.

2
Patrick Pijnappel

une solution consiste à utiliser un NSArray et à le créer à partir de zéro en insérant et en supprimant, comme

- (void)addSomeObject:(id)object {
    self.myArray = [self.myArray arrayByAddingObject:object];
}

- (void)removeSomeObject:(id)object {
    NSMutableArray * ma = [self.myArray mutableCopy];
    [ma removeObject:object];
    self.myArray = ma;
}

que vous obtenez le KVO et pouvez comparer l'ancien et le nouveau tableau

REMARQUE: self.myArray ne doit pas être nil, sinon arrayByAddingObject: renvoie nil aussi

Selon le cas, cela pourrait être la solution, et comme NSArray stocke uniquement des pointeurs, cela ne représente pas une surcharge, à moins que vous ne travailliez avec de grands tableaux et des opérations fréquentes

0
Peter Lapisu

Vous devez insérer votre appel addObject: dans des appels willChangeValueForKey: et didChangeValueForKey:. Autant que je sache, il est impossible pour le NSMutableArray que vous modifiiez de savoir que des observateurs observent son propriétaire.

0
Ben Gottlieb