KVO: Observer un NSArray sous conditions

laurrislaurris Membre
22:59 modifié dans API AppKit #1
Voilà  la situation:
J'ai une collection d'objets mise sous observation KVO avec addObserver:...
J'utilise le handler -(void)observeValueForKeyPath ... pour appeler une méthode X après une Insertion ou après une suppression d'objet (selon les valeurs du change dictionary).

Voilà  ce que je voudrais faire:
J'ai besoin de faire une suppression, puis une insertion et seulement APRES appeller la méthode X. En l'état, cette méthode serait appelée 2 fois.

Je pourrais utiliser une ivar, modifiée avant chaque changement dans la collection, qui dirait appellle_la_methode_x = YES / NO. Mais ça se révèle trop compliqué et pas assez fiable.

Est-ce que vous savez quelle est la bonne approche ?
J'ai pensé utiliser le Context dans addObserver:self
forKeyPath:key
options:nil
context:Context

Mais dans ce cas, pour permettre les différents cas possibles, il faudrait que la collection soit observée par deux observers en même temps (avec des context différents). Est-ce autorisé par Mr Cocoa ?

Mes hommages,
Laurris.

Réponses

  • AliGatorAliGator Membre, Modérateur
    22:59 modifié #2
    Un petit tour du côté de la doc ?

    Un appel explicite à  will/didChange plutôt qu'une surveillance automatique ?
    Ou alors dans le cas de ta suppression puis insertion, ne pas appeler les setter/getters ([... setValue:...] etc) mais directement les valeurs de ta classe sans setter/getter (pour que la notification willChange/didCHange ne soit pas rajouté dans le code et appelé automatiquement) ?

    Ce ne sont que des idées en vrac, mais bon comme si tu n'appelles pas les méthodes get/set "setVariable" et "variable" mais modifies la _variable directement ça n'ajoutera pas willChangeValueForKey et didChangeValueForKey tout seul, ça peut être à  exploiter.
  • laurrislaurris Membre
    22:59 modifié #3
    Ouaip merci. J'étais complètement passé à  coté de la méthode
    <br />[self willChange:NSKeyValueChangeKind  <br />     valuesAtIndexes:indexes forKey:@&quot;key&quot;]<br />
    


    Ca permet de réarranger son array à  loisir dans son coin, puis de mettre en route l'observation avec en argument un NSKeyValueChangeKind particulier. C'est ce dernier point qui m'est utile dans mon cas puisque je ne veux pas que l'obervateur interprète mon changement comme une insertion ou une suppression. J'ai pas encore essayé mais c'est sans doute ce qui convient.

    Bon, en réalité un autre problème est apparu entre-temps qui m'empêche de tester cette solution (comme dab). J'explique: mon réarrangement consiste en un changement d'index de mes objet à  l'intérieur de l'array. On dirait qu'il n'existe pas de méthode NSMutableArray toute faite pour ça, ce qui est surprenant. Je cheche quelque chose du genre:
    <br />- (void)moveObjectFromIndex:(int)oldindex toIndex:(int)newIndex<br />
    


    Je suis sûr (ne niez pas !) que vous avez déjà  fait un truc comme ça ... et je sens que je vais m'arracher les cheveux avec les replacements d'index. Alors, à  vot' bon coeur sieu' dames ...


  • AliGatorAliGator Membre, Modérateur
    22:59 modifié #4
    Déjà  est-ce une utilisation isolée du déplacement de ton objet dans ton tableau ?
    Ou est-ce que tu en fais en série ? J'imagine que ce n'est pas juste pour trier ton tableau, tu aurais utilisé les méthodes "sortXXX" avec le critère de tri de ton choix, aussi alambiqué soit-il.

    Je n'ai pas de méthode tout faite, mais comme ça au feeling, il faut au moins déjà  penser à  l'ordre des opérations :
    • Si tu veux déplacer l'élément d'index 5 en position 2, il faut évidemment enlever l'élément (position 5) avant de l'insérer en position 2, ou alors si tu l'insères d'abord, il faut ensuite le retirer de la position... 6, puisque ça aura décalé les index entre temps.
    • Et si tu l'enlèves de la position 5 avant de l'insérer en position 2, penser à  faire un retain entre temps, puisque lorsque tu le retires de ton tableau, il va recevoir un "release" et si son retainCount n'était que de 1 car il n'était retenu que par le tableau en question, il sera détruit avant que tu n'aies le temps de le réinsérer, si je ne me trompe...
    • Si tu veux déplacer l'élément d'index 5 en position 10, il faut alors peut-être plus logiquement l'insérer en position 10 avant de le retirer de la position 5. Ou alors le supprimer de la position 5 (après avoir fait un retain), puis l'insérer à  la position 9, à  cause du décalage d'indices (puis lui envoyer le release balançant le "retain" précédemment envoyé)...


    Bref, il faut bien définir ce que tu entends par "mettre l'élément qui est à  la position X en position Y", les positions, et en particulier Y, pour toi c'est l'index "actuel", ou après la suppression de l'élément de sa position X et avant la réinsertion ?


    A mon avis d'ailleurs c'est aussi pour tout ce genre d'ambigà¼ités dans la définition qu'il n'y a pas de méthodes prédéfinies :
    - Il y a de quoi enlever un élément à  une position X, aussi de quoi en insérer un à  une position Y, et même de quoi échanger les éléments aux positions X et Y entre eux... mais replacer un élément X à  la position Y, la définition en particulier dudit Y n'est pas suffisament claire.
    - Alors que si tu le fais manuellement 2 temps (enlèvement de la valeur puis insertion... ou dans l'autre sens), tu sais explicitement dans quel sens tu fais les opérations.

    ---

    Une solution la plus propre étant à  mon avis de récupérer (sans l'extraire) l'élément à  la position X ([tt]id obj = [tableau valueAtIndex:X];[/tt]), pour pouvoir l'insérer en position Y ([tt][tableau insertObject:obj atIndex:Y];[/tt]), et enfin le supprimer de sa position initial, mais en prennant soin de vérifier le décalage d'indice ([tt][tableau removeObjectAtIndex: (X<Y)?X:X+1];[/tt])

    Ca ne résoud pas ton problème de double-notification, mais déjà  sans ce souci tu vois qu'il faut penser à  certains détails ;)
  • laurrislaurris Membre
    février 2007 modifié #5
    dans 1171577689:

    Déjà  est-ce une utilisation isolée du déplacement de ton objet dans ton tableau ?
    Ou est-ce que tu en fais en série ? J'imagine que ce n'est pas juste pour trier ton tableau, tu aurais utilisé les méthodes "sortXXX" avec le critère de tri de ton choix, aussi alambiqué soit-il.

    Je n'ai pas de méthode tout faite, mais comme ça au feeling, il faut au moins déjà  penser à  l'ordre des opérations :
    • Si tu veux déplacer l'élément d'index 5 en position 2, il faut évidemment enlever l'élément (position 5) avant de l'insérer en position 2, ou alors si tu l'insères d'abord, il faut ensuite le retirer de la position... 6, puisque ça aura décalé les index entre temps.
    • Et si tu l'enlèves de la position 5 avant de l'insérer en position 2, penser à  faire un retain entre temps, puisque lorsque tu le retires de ton tableau, il va recevoir un "release" et si son retainCount n'était que de 1 car il n'était retenu que par le tableau en question, il sera détruit avant que tu n'aies le temps de le réinsérer, si je ne me trompe...
    • Si tu veux déplacer l'élément d'index 5 en position 10, il faut alors peut-être plus logiquement l'insérer en position 10 avant de le retirer de la position 5. Ou alors le supprimer de la position 5 (après avoir fait un retain), puis l'insérer à  la position 9, à  cause du décalage d'indices (puis lui envoyer le release balançant le "retain" précédemment envoyé)...


    Bref, il faut bien définir ce que tu entends par "mettre l'élément qui est à  la position X en position Y", les positions, et en particulier Y, pour toi c'est l'index "actuel", ou après la suppression de l'élément de sa position X et avant la réinsertion ?


    A mon avis d'ailleurs c'est aussi pour tout ce genre d'ambigà¼ités dans la définition qu'il n'y a pas de méthodes prédéfinies :
    - Il y a de quoi enlever un élément à  une position X, aussi de quoi en insérer un à  une position Y, et même de quoi échanger les éléments aux positions X et Y entre eux... mais replacer un élément X à  la position Y, la définition en particulier dudit Y n'est pas suffisament claire.
    - Alors que si tu le fais manuellement 2 temps (enlèvement de la valeur puis insertion... ou dans l'autre sens), tu sais explicitement dans quel sens tu fais les opérations.

    ---

    Une solution la plus propre étant à  mon avis de récupérer (sans l'extraire) l'élément à  la position X ([tt]id obj = [tableau valueAtIndex:X];[/tt]), pour pouvoir l'insérer en position Y ([tt][tableau insertObject:obj atIndex:Y];[/tt]), et enfin le supprimer de sa position initial, mais en prennant soin de vérifier le décalage d'indice ([tt][tableau removeObjectAtIndex: (X<Y)?X:X+1];[/tt])

    Ca ne résoud pas ton problème de double-notification, mais déjà  sans ce souci tu vois qu'il faut penser à  certains détails ;)


    Merci pour ta réponse très détaillée. Il s'agit d'un réarrangement manuel à  l'intérieur d'une collection. Comme pour le dra'n drop de lignes à  l'intérieur d'un tableau.
    Donc en réalité, je ne sais pas à  l'avance si l'index source < index destination. Il faut que je prenne en compte les deux cas.

    Je vais essayer ta méthode insertion+suppression. Juste un truc qui m'étonne: si j'insère un objet de l'array dans l'array, il ne faut pas faire une copie avant ? Sinon on aurait le même objet à  deux endroits différents , ce qui pose de graves problèmes déontologiques, je crois.

    Edit: ça a l'air de marcher impec. Voici le code si ça peut être utile:
    <br />const NSKeyValueChangeReordering = 10;<br />- (void)moveObjectInSlicesFromIndex:(int)sourceIndex toIndex:(int)destIndex {<br />&nbsp; &nbsp; NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:sourceIndex];<br />&nbsp; &nbsp; [self willChange:NSKeyValueChangeReordering valuesAtIndexes:indexes forKey:@&quot;slices&quot;];<br />&nbsp; &nbsp; [slices insertObject:[slices objectAtIndex:sourceIndex] atIndex:destIndex];&nbsp; &nbsp; <br />&nbsp; &nbsp; [slices removeObjectAtIndex: (sourceIndex&lt;destIndex)?sourceIndex:sourceIndex+1];&nbsp; &nbsp; <br />&nbsp; &nbsp; [self didChange:NSKeyValueChangeReordering valuesAtIndexes:indexes forKey:@&quot;slices&quot;];<br />}<br />
    


    (Le drop d'une sélection multiple la prochaine fois, à  chaque jour suffit sa peine)

    Quant à  savoir pourquoi l'auto-insertion d'un objet de l'array a l'air de marcher, je suppose que c'est parce que l'array fait lui-même une copie des objets avant de les insérer. Mais si quelqu'un peut confirmer celà , je n'en serai que plus rassuré.


  • AliGatorAliGator Membre, Modérateur
    février 2007 modifié #6
    Hello,

    1) Merci d'éviter de faire une citation de tout le message précédent pour rien, ça surcharge le forum et n'a aucune utilité ! Le but des citations est plutôt de citer une partie précise d'un message poru répondre à  une question précise, pas de dupliquer la quantité de texte sur les forums !

    2) Sinon je ne vois pas ce qui te gène dans l'idée d'avoir 2 fois le même objet dans ton tableau ? Ce ne sont que des références.
    le NSArray "retain" les éléments qu'il contient, tout comme tous les containers de Cocoa.

    Un code d'exemple vaut mieux qu'un long discours :
    NSMutableString* monObj = [[NSMutableString alloc] initWithString:@&quot;Coucou&quot;]; // retainCount de monObj = 1<br />NSMutableArray* tab = [[NSMutableArray alloc] init];<br /><br />[tab addObject: monObj]; // ajoute une référence à  monObj dans le tableau tab<br />// à  ce stade, monObj a un retainCount de 2, il est retenu 1 fois par le tableau et une fois par monObj<br />NSLog(@&quot;Le tableau contient : %@ ; retainCount = %u&quot; , tab, [monObj retainCount]);<br /><br />[tab addObject: monObj]; // ajoute une 2e référence à  monObj dans tab<br />// à  ce stade, monObj a un retainCount de 3, il est retenu 2 fois par le tableau et 1 fois par monObj<br />NSLog(@&quot;Le tableau contient : %@ ; retainCount = %u&quot; , tab, [monObj retainCount]);<br /><br />[monObj setString:@&quot;Salut&quot;]; // remplace le contenu de monObj<br />// à  ce stade, tab contient 2 références à  monObj, mais ce ne sont que des références vers le même objet !!<br />// Donc leur contenu a changé aussi !<br />NSLog(@&quot;Le tableau contient : %@ ; retainCount = %u&quot; , tab, [monObj retainCount]);<br /><br />[monObj release]; // on envoie un release à  l&#39;objet, mais il est encore retenu (et même 2x) par le tableau<br />NSLog(@&quot;Le tableau contient : %@ ; retainCount = %u&quot; , tab, [[tab objectAtIndex:0] retainCount]);<br /><br />// et enfin on libère le tableau<br />[tab release]; // cela envoie aussi un release à  chaque élément que le tableau contient<br />// donc à  ce stade le retainCount de l&#39;objet passe de 2 à  0 --&gt; l&#39;objet est détruit de la mémoire<br />// (un release envoyé pour l&#39;élément 1 du tableau et un pour l&#39;élément 2, qui pointe en fait sur le même)
    
    Tu remarqueras que quand je change la chaà®ne de monObj, les 2 chaines dans le tableau changent aussi (puisque c'est 3x le même objet, et pas 3 copies différentes), ce qui montre que ce ne sont que des références qui sont ajoutées dans le tableau, pas des copies. C'est le mécanisme de retain/release (et donc le retainCount) qui gèrent le tout.

    Comme d'hab, quoi, tout est dans la doc :
    And when you add an object to an Objective-C array, the object isn?t copied, but rather receives a retain message before its id is added to the array. When an array is deallocated, each element is sent a release message.


    Conclusion : il n'y a jamais eu de limitation interdisant de mettre plusieurs fois le même objet dans un NSArray, tout comme il n'a jamais été interdit d'avoir plusieurs variables pointant vers / utilisant le même objet !
  • 22:59 modifié #7
    En plus de ce qu'a dit Ali, j'ajouterais même qu'il existe une méthode répondant au doux nom de [tt]indexOfObjectIdenticalTo:inRange:[/tt] qui a justement été faite pour trouver les différentes occurences d'un même objet dans un tableau (cette méthode fait sa comparaison sur base des adresses mémoire, contrairement à  [tt]indexOfObject:inRange:[/tt] qui vérifie l'égalité des valeurs). Donc si cette méthode existe, c'est que c'est permis, et même prévu.
  • laurrislaurris Membre
    22:59 modifié #8
    Merci pour ces éclaircissements. Je retiens que l'objet n'est pas copié mais retenu et je le copierai cent fois !
  • schlumschlum Membre
    22:59 modifié #9
    dans 1171651061:

    Merci pour ces éclaircissements. Je retiens que l'objet n'est pas copié mais retenu et je le copierai cent fois !

    Du coup, fais attention aux effets de bord si t'as le même objet "mutable" plusieurs fois dans un tableau et que t'en modifies un  ;)
  • laurrislaurris Membre
    mars 2007 modifié #10
    Comme j'ai commencé ce fil, je donne une solution à  ce problème. Ca vient d'un exemple Apple donc plutôt digne de foi.

    Cette méthode permet de réarranger manuellement plusieurs objets à  l'intérieur d'une collection et de façon conforme au KVO. Si l'objet est observé, il se signale par un type de changement NSKeyValueReordering qui est une constante prédéfinie.
    Remplacer <key> par le chemin de l'objet par rapport à  SELF.

    <br />-(void) moveObjectsIn&lt;key&gt;FromIndexes:(NSIndexSet*)indexSet toIndex:(int)insertIndex<br />{<br />	[self willChange:NSKeyValueChangeReordering valuesAtIndexes:indexSet forKey:&lt;key&gt;];<br />	<br />	NSMutableArray		*objects = [self &lt;key&gt;];<br />	int			index = [indexSet lastIndex];<br />	<br />&nbsp; &nbsp; int			aboveInsertIndexCount = 0;<br />&nbsp; &nbsp; id			object;<br />&nbsp; &nbsp; int			removeIndex;<br />	<br />&nbsp; &nbsp; while (NSNotFound != index) {<br />		if (index &gt;= insertIndex) {<br />			removeIndex = index + aboveInsertIndexCount;<br />			aboveInsertIndexCount += 1;<br />		}<br />		else {<br />			removeIndex = index;<br />			insertIndex -= 1;<br />		}<br />		object = [objects objectAtIndex:removeIndex];<br />		[objects removeObjectAtIndex:removeIndex];<br />		[objects insertObject:object atIndex:insertIndex];<br />		<br />		index = [indexSet indexLessThanIndex:index];<br />		}<br />		<br />	[self didChange:NSKeyValueChangeReordering valuesAtIndexes:indexSet forKey:&lt;key&gt;];<br />}<br /><br />
    
Connectez-vous ou Inscrivez-vous pour répondre.