[Résolu] Core Data beginUndoGrouping / endUndoGrouping: j'ai dû mal comprendre...

berfisberfis Membre
septembre 2016 modifié dans API AppKit #1

Bonjour,


 


Dans une NSTableView, j'offre la possibilité d'ajouter, de retirer et de réordonnancer les colonnes. Chaque NSTableColumn possède une référence à  l'entité Core Data "Column" stockée dans sa headerCell.representedObject.


 


L'entité elle-même possède un attribut "rank" qui mémorise la position de la colonne dans la tableView.


 


De fait, tout cela fonctionne parfaitement... jusqu'au "undo", qui est réglé dans l'entité Column:



- (void) awakeFromSnapshotEvents:(NSSnapshotEventType)flags
{
[super awakeFromSnapshotEvents:flags];
[[NSNotificationCenter defaultCenter] postNotificationName:@ColumnModification object:self userInfo:[NSDictionary dictionaryWithObject:@(flags) forKey:@flags ]];
}

Si j'ajoute au retire une colonne, si je change son titre, le undo fait son travail. Le problème survient au undo d'un déplacement de colonne.


 


Il est impossible de ne changer le rang que d'une colonne: le rang des autres est forcément modifié aussi. Je renumérote donc toutes les entités à  chaque changement de colonne, mais comme je veux qu'un seul undo rétablisse l'ordre initial, je groupe les modifications, comme ceci:



- (void)tableViewColumnDidMove:(NSNotification *)aNotification
{
[[self.managedObjectContext undoManager] beginUndoGrouping];
for (long rank =0; rank<self.tableview.tableColumns.count; rank++)
{
NSTableColumn *tableColumn = self.tableview.tableColumns[rank];
[(Column *)tableColumn.headerCell.representedObject setRank:rank];
}
[[self.managedObjectContext undoManager] endUndoGrouping];
}

Mais le undo ne se comporte pas comme prévu: j'entre dans une boucle sans fin, et le contrôleur réinsère les objets, la fonction awakeFromSnapshotEvents est appelée, qui lance une notification"columnModification", puis le contrôleur réinsère les objets, etc...



- (void) columnModification: (NSNotification*)notification
{
{
while (self.tableview.tableColumns.count>0) {
[self.tableview removeTableColumn:self.tableview.tableColumns[0]];
}
[self prepareContent];
[self.tableview reloadData];
}
}

Qu'est-ce que j'ai encore manqué?


Réponses

  • CéroceCéroce Membre, Modérateur
    septembre 2016 modifié #2
    Sachant qu'un groupe est créé à  chaque passage dans la boucle d'événements, j'aurais dit que tu n'a pas forcément besoin de le créer. Utilises-tu NSManagedDocument ? Si oui, son undoManager doit être le même que celui du MOC.

    Note aussi que si je ne me trompe pas, annuler ne va pas replacer les colonnes, puisque ça va juste changer le "rank" interne.

    Sinon, sache que la gestion de l'undo par Core Data est abominable. J'avais essayé de logger les écritures dans l'undo manager, mais il fait son bricolage interne, c'est indébuggable, et ça ne fonctionne pas en test unitaire.

    Voir aussi ici:
    http://mikeabdullah.net/core_data_undo_management.html
    http://blog.wilshipley.com/2007/12/transitions-and-epiphanies.html
  • berfisberfis Membre
    septembre 2016 modifié #3

    Bonjour Céroce,


     


    Oui, j'utilise NSManagedDocument, et le ColumnController à  un outlet sur son contexte. Le undoManager est le même, j'ai vérifié.


     


    Non, le undo ne va pas replacer les colonnes, voila pourquoi je:


    - supprime toutes les colonnes (les NSTableColumns, pas les entités)


    - les recrée dans le "prépareContent" du contrôleur.


     


    D'ailleurs, le undo fonctionne parfaitement pour ADD, REMOVE et RESIZE.


  • CéroceCéroce Membre, Modérateur
    Mais qu'est-ce qui provoque la boucle infinie ? Dans la call stack tu dois voir l'ordre des fonctions, et tu vas pouvoir trouver quelle méthode est appelée, alors que tu ne t'y attends pas.
  • berfisberfis Membre
    septembre 2016 modifié #5

    Voici les deux routines qui sont appelées à  tour de rôle, sans fin:



    - (void) prepareContent
    {
    [self.studentController fetchWithRequest:nil merge:NO error:nil];
    [super prepareContent];
    [self fetchWithRequest:nil merge:NO error:nil];
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@Column inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@rank ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    [fetchRequest setSortDescriptors:sortDescriptors];

    NSArray *array = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];

    if (array.count)
    for (long i = 0; i<array.count; i++)
    [self insertNewColumnWithObject:array[i]];
    NSLog(@Content prepared);
    [self.studentController addObserver:self forKeyPath:@selectedObjects options:0 context:nil];
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(columnModification:) name:@ColumnModification object:nil];
    }

    et



    - (void) columnModification: (NSNotification*)notification
    {
    {
    while (self.tableview.tableColumns.count>0)
    [self.tableview removeTableColumn:self.tableview.tableColumns[0]];

    [self prepareContent];
    [self.tableview reloadData];
    }
    }

    Je n'y comprends rien. On dirait que awakeFromSnapshotEvents est appelée sans nécessité...


     


    EDIT:


     


    Ooops... je crois que j'ai trouvé... c'est:



    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(columnModification:) name:@ColumnModification object:nil]; 

    qui rajoutait un observateur... qui recevait le message de modification... qui rajoutait l'observateur.


     

    Je croyais que le NSNotificationCenter n'ajoutait qu'une instance d'un observateur, à  la manière d'un NSSet, mais il le fait plutôt à  la manière d'une NSMutableArray...

Connectez-vous ou Inscrivez-vous pour répondre.