12

Je suis après quelques conseils sur l'utilisation de NSOperation et le dessin:NSOpération bloque la peinture de l'interface utilisateur?

J'ai un fil conducteur créer ma sous-classe NSOperation, ce qui ajoute à un NSOperationQueue.

Mon NSOperation fait du gros traitement, il est prévu de boucler sa méthode main() pendant plusieurs minutes, en traitant constamment du travail, mais pour l'instant je n'ai qu'une boucle while() avec un sleep (1) à l'intérieur, qui est configuré pour se déplacer seulement 5 fois (pour tester).

Le thread principal (original) qui engendre ce NSOperation est chargé de dessiner à une vue et mettre à jour l'interface utilisateur. Je souhaitais que le thread NSOperation utilise une notification pour indiquer au thread principal qu'il avait effectué une certaine quantité de traitement, au moment où cette notification est envoyée une fois chaque fois qu'elle passe par sa boucle while() (c'est-à-dire; une fois par seconde parce que c'est juste en train de dormir (1)). Le thread principal (la vue), s'inscrit pour recevoir ces notifications.

Les notifications parviennent immédiatement au thread principal et paraissent asynchrones. Il semble que les deux threads s'exécutent comme prévu ... c'est-à-dire simultanément. (J'utilise NSLog() juste pour vérifier approximativement quand chaque thread envoie et reçoit la notification). Lorsque la vue reçoit une notification et que sa méthode gestionnaire est appelée, j'incrémente simplement une variable entière et j'essaie de la dessiner dans la vue (en tant que chaîne bien sûr). Lors du test, le code de drawRect: dessine cet entier (en tant que chaîne de caractères) sur l'écran.

Cependant: voici mon problème (désolé, il faut un peu de temps pour arriver ici): quand le thread principal (vue) reçoit une notification de NSOperation, il met à jour cet entier de test et appelle [self setNeedsDisplay]. Cependant, la vue ne se redessine pas tant que NSOperation n'est pas terminée! Je m'attendais à ce que NSOperation, étant un thread séparé, n'aurait aucune possibilité de bloquer la boucle d'événement du thread principal, mais il semble que c'est ce qui se passe. Lorsque NSOperation se termine et que son main() revient, la vue se redessine immédiatement. Peut-être que je n'utilise pas correctement NSOperation. Je l'utilise en mode "non simultané", mais malgré le nom, je comprends que cela produit encore un nouveau thread et permet un traitement asynchrone.

Toute aide ou conseil très apprécié, si vous souhaitez voir du code, faites le moi savoir.

Répondre

10

La méthode dans l'observateur qui est effectuée en réponse à votre notification n'est pas effectuée sur le thread principal. Ainsi, dans cette méthode, vous pouvez forcer l'exécution d'une autre méthode sur le thread principal à l'aide de performSelectorOnMainThread:withObject:waitUntilDone:.

Par exemple:

MyOperation.m

- (void)main { 
    for (int i = 1; i <= 5; i++) { 
     sleep(1); 
     [[NSNotificationCenter defaultCenter] postNotificationName:@"GTCNotification" object:[NSNumber numberWithInteger:i]]; 
    } 
} 

MyViewController.m

- (void)setupOperation { 
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(myNotificationResponse:) name:@"GTCNotification" object:nil]; 

    NSOperationQueue *opQueue = [[NSOperationQueue alloc] init]; 
    MyOperation *myOp = [[MyOperation alloc] init]; 

    [opQueue addOperation:myOp]; 

    [myOp release]; 
    [opQueue release]; 
} 

- (void)myNotificationResponse:(NSNotification*)note { 
    NSNumber *count = [note object]; 
    [self performSelectorOnMainThread:@selector(updateView:) withObject:count waitUntilDone:YES]; 
} 

- (void)updateView:(NSNumber*)count { 
    countLabel.text = count.stringValue; 
} 
+3

Vous pouvez le faire (performSelectorOnMainThread: withObject: waitUntilDone :) directement à partir de votre méthode principale NSOperation si vous voulez - sans toutes ces choses de notification. – grzaks

0

La plupart des gens savent que toutes les tâches relatives à l'affichage doit se faire sur la principale fil.Cependant, en conséquence directe, toute modification d'une propriété de liaison Cocoa pouvant affecter le dessin doit également être modifiée sur le thread principal (car les déclencheurs KVO seront traités sur le thread à partir duquel ils sont déclenchés). C'est une surprise pour la plupart des gens: en particulier, il n'est pas sûr d'appeler [self setNeedsDisplay] à partir d'un thread autre que le thread principal.

Comme mentionné par gerry, votre NSNotification n'est pas traitée sur le thread principal, il est traité sur le thread à partir de laquelle il est envoyé. Par conséquent, dans votre gestionnaire NSNotification, vous devez renvoyer vos commandes au thread principal si elles sont liées à l'affichage ou si elles affectent les liaisons Cocoa. @performSelector fonctionne, mais avec des files d'attente, j'ai trouvé une méthode plus simple et plus lisible:

[ [ NSOperationQueue mainQueue] addOperationWithBlock:^(void) { 
    /* Your main-thread code here */ }]; 

Il évite la définition d'une fonction d'assistance secondaire dont le seul but est d'être appelé à partir du thread principal. mainQueue n'a été défini que dans> 10.6.

Questions connexes