web-dev-qa-db-fra.com

Comment planifier un bloc pour qu'il s'exécute à la prochaine itération de la boucle d'exécution?

Je veux pouvoir exécuter une block à la prochaine itération de la boucle d'exécution. Peu importe qu’elle soit exécutée au début ou à la fin de la prochaine boucle d’exécution, elle est simplement différée jusqu’à ce que tout le code de la boucle d’exécution actuelle ait été exécuté.

Je sais que ce qui suit ne fonctionne pas car il est entrelacé avec la boucle d'exécution principale. Mon code pourrait donc s'exécuter lors de la prochaine boucle d'exécution, mais pas nécessairement.

dispatch_async(dispatch_get_main_queue(),^{
    //my code
});

Ce qui suit, je crois, souffre du même problème que ci-dessus:

dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^(void){
    //my code
});

Maintenant, je crois ce qui suit fonctionnerait tel qu’il est placé à la fin de la boucle courante (corrigez-moi si je me trompe), cela fonctionnerait-il réellement?

[self performSelector:@selector(myMethod) withObject:nil afterDelay:0];

Qu'en est-il d'une minuterie avec un intervalle 0? La documentation indique: If seconds is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead. Cela signifie-t-il que l'exécution sera garantie lors de la prochaine itération de la boucle d'exécution?

[NSTimer scheduledTimerWithTimeInterval:0 target:self selector:@selector(myMethod) userInfo:nil repeats:NO];

C’est toutes les options auxquelles je peux penser, mais je ne suis pas encore plus près d’exécuter un bloc (par opposition à l’appel d’une méthode) lors de la prochaine itération de la boucle d’exécution, avec la garantie que ce ne sera pas avant.

34
lms

Vous pourriez ne pas être au courant de tout ce que la boucle d’exécution fait à chaque itération. (Je n'étais pas avant d'avoir recherché cette réponse!) En l'occurrence, CFRunLoop fait partie du paquetage open source CoreFoundation , afin que nous puissions jeter un coup d'œil à ce que cela implique. La boucle d'exécution ressemble à peu près à ceci:

while (true) {
    Call kCFRunLoopBeforeTimers observer callbacks;
    Call kCFRunLoopBeforeSources observer callbacks;
    Perform blocks queued by CFRunLoopPerformBlock;
    Call the callback of each version 0 CFRunLoopSource that has been signalled;
    if (any version 0 source callbacks were called) {
        Perform blocks newly queued by CFRunLoopPerformBlock;
    }
    if (I didn't drain the main queue on the last iteration
        AND the main queue has any blocks waiting)
    {
        while (main queue has blocks) {
            perform the next block on the main queue
        }
    } else {
        Call kCFRunLoopBeforeWaiting observer callbacks;
        Wait for a CFRunLoopSource to be signalled
          OR for a timer to fire
          OR for a block to be added to the main queue;
        Call kCFRunLoopAfterWaiting observer callbacks;
        if (the event was a timer) {
            call CFRunLoopTimer callbacks for timers that should have fired by now
        } else if (event was a block arriving on the main queue) {
            while (main queue has blocks) {
                perform the next block on the main queue
            }
        } else {
            look up the version 1 CFRunLoopSource for the event
            if (I found a version 1 source) {
                call the source's callback
            }
        }
    }
    Perform blocks queued by CFRunLoopPerformBlock;
}

Vous pouvez constater qu’il existe une grande variété de façons d’accrocher la boucle d’exécution. Vous pouvez créer une CFRunLoopObserver à appeler pour n’importe laquelle des «activités» de votre choix. Vous pouvez créer une version 0 CFRunLoopSource et la signaler immédiatement. Vous pouvez créer une paire connectée de CFMessagePorts, en envelopper une dans une version 1 CFRunLoopSource et lui envoyer un message. Vous pouvez créer une CFRunLoopTimer. Vous pouvez mettre les blocs en file d'attente en utilisant dispatch_get_main_queue ou CFRunLoopPerformBlock.

Vous devrez choisir laquelle de ces API utiliser lorsque vous planifiez le blocage et à quel moment vous souhaitez qu'il soit appelé.

Par exemple, les touchers sont gérés dans une source de version 1, mais si vous le manipulez en mettant à jour l'écran, cette mise à jour n'est pas réellement effectuée jusqu'à ce que la transaction Core Animation soit validée, ce qui se produit dans un observateur kCFRunLoopBeforeWaiting.

Supposons maintenant que vous souhaitiez planifier le blocage pendant que vous manipulez le contact, mais que vous souhaitiez qu'il soit exécuté une fois la transaction validée.

Vous pouvez ajouter votre propre CFRunLoopObserver pour l'activité kCFRunLoopBeforeWaiting, mais cet observateur peut s'exécuter avant ou après l'observateur de Core Animation, en fonction de l'ordre que vous spécifiez et de l'ordre spécifié par Core Animation. (Core Animation spécifie actuellement une commande de 2000000, mais cela n’est pas documenté et pourrait donc changer.)

Pour vous assurer que votre bloc fonctionne après l'observateur de Core Animation, même si votre observateur exécute avant l'observateur de Core Animation, n'appelez pas le bloc directement dans le rappel de votre observateur. Utilisez plutôt dispatch_async à ce stade pour ajouter le bloc à la file d'attente principale. Mettre le bloc sur la file d'attente principale force la boucle d'exécution à sortir immédiatement de son attente. Il exécutera tous les observateurs kCFRunLoopAfterWaiting, puis videra la file d'attente principale; il lancera alors votre bloc.

81
rob mayoff

La réponse de Rob est excellente et informative. Je n'essaie pas de le remplacer.

En lisant simplement la documentation UIView , j'ai trouvé:

achèvement  

Un objet de bloc à exécuter lorsque la séquence d'animation Se termine. Ce bloc n'a aucune valeur de retour et prend un seul argument booléen Qui indique si les animations réellement Se sont terminées avant l'appel du gestionnaire d'achèvement. Si la durée de De l'animation est 0, ce bloc est exécuté au début du cycle de Cycle suivant. Ce paramètre peut être NULL.

Donc, une solution facile serait:

UIView.animate(withDuration: 0) {
    // anything
}
0
GaétanZ

Je ne pense pas qu’il existe d’API permettant de garantir l’exécution du code lors du prochain tour de la boucle d’événement. Je suis également curieux de savoir pourquoi vous avez besoin d’une garantie que rien d’autre n’a fonctionné, le principal en particulier.

Je peux également confirmer que l'utilisation du perforSelector: withObject: afterDelay utilise un minuteur basé sur la boucle d'exécution et aura un comportement similaire à dispatch_async'ing sur dispatch_get_main_queue ().

modifier:

En fait, après avoir relu votre question, il semblerait que vous n’ayez besoin que du tour current runloop. Si cela est vrai, alors dispatch_async est exactement ce dont vous avez besoin. En fait, tout le code ci-dessus garantit que le tour current runloop sera terminé.

0
Mattie