web-dev-qa-db-fra.com

Différences de comportement entre performBlock: et performBlockAndWait :?

Je crée un NSManagedObjectContext dans une file d'attente privée pour gérer les mises à jour de données que je prends des fichiers et/ou des services:

NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
privateContext.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator;

Étant donné que j'utilise une file d'attente privée, je ne comprends pas complètement la différence entre performBlock: et performBlockAndWait: méthodes ... Pour effectuer mes mises à jour de données, je fais actuellement ceci:

[privateContext performBlock: ^{

        // Parse files and/or call services and parse
        // their responses

        // Save context
        [privateContext save:nil];

        dispatch_async(dispatch_get_main_queue(), ^{
            // Notify update to user
        });
    }];

Dans ce cas, mes mises à jour de données sont effectuées de manière synchronisée et séquentielle, donc je suppose que c'est le bon endroit pour enregistrer le contexte, non? Si je fais quelque chose de mal, je vous serais reconnaissant de me le faire savoir. D'un autre côté, ce code serait-il équivalent?:

[privateContext performBlockAndWait: ^{

        // Parse files and/or call services and parse
        // their responses

        // Save context
        [privateContext save:nil];
    }];

// Notify update to user

Encore une fois, je suppose que c'est le bon endroit pour enregistrer le contexte ... quelles sont les différences entre les deux méthodes (le cas échéant, dans ce cas)?

Que se passe-t-il si, au lieu d'effectuer des appels de service synchrones ou l'analyse de fichiers, j'ai besoin d'effectuer des appels de service asynchrones? Comment ces mises à jour de données seraient-elles gérées?

Merci d'avance

26
AppsDev

Vous avez raison en ce que tout ce que vous voulez faire avec un MOC doit être fait dans performBlock ou performBlockAndWait. Notez que la conservation/libération est thread-safe pour les objets gérés, vous n'avez donc pas besoin d'être à l'intérieur d'un de ces blocs pour conserver/libérer le nombre de références sur les objets gérés.

Ils utilisent tous deux une file d'attente synchrone pour traiter les messages, ce qui signifie qu'un seul bloc s'exécutera à la fois. Eh bien, c'est presque vrai. Voir les descriptions de performBlockAndWait. Dans tous les cas, l'accès au MOC sera sérialisé de telle sorte qu'un seul thread accède au MOC à la fois.

tl; dr Ne vous inquiétez pas de la différence et utilisez toujours performBlock.

Différences factuelles

Il existe un certain nombre de différences. Je suis sûr qu'il y en a plus, mais voici ceux que je pense être les plus importants à comprendre.

Synchrone vs asynchrone

performBlock est asynchrone, en ce sens qu'il revient immédiatement, et le bloc est exécuté à un moment donné dans le futur, sur un thread non divulgué. Tous les blocs donnés au MOC via performBlock s'exécuteront dans l'ordre où ils ont été ajoutés.

performBlockAndWait est synchrone, en ce sens que le thread appelant attend l'exécution du bloc avant de revenir. Que le bloc s'exécute dans un autre thread ou s'exécute dans le thread appelant n'est pas si important et constitue un détail d'implémentation qui ne peut pas être approuvé.

Notez, cependant, qu'il pourrait être implémenté comme "Hé, un autre thread, allez exécuter ce bloc. Je vais rester ici sans rien faire jusqu'à ce que vous me disiez que c'est fait." Ou, il pourrait être implémenté comme "Hey, Core Data, donnez-moi un verrou qui empêche tous ces autres blocs de s'exécuter afin que je puisse exécuter ce bloc sur mon propre thread." Ou il pourrait être mis en œuvre d'une autre manière. Encore une fois, les détails de mise en œuvre, qui pourraient changer à tout moment.

Je vous le dirai cependant, la dernière fois que je l'ai testé, performBlockAndWait a exécuté le bloc sur le thread appelant (ce qui signifie la deuxième option dans le paragraphe ci-dessus). Ce ne sont vraiment que des informations pour vous aider à comprendre ce qui se passe et ne doivent en aucun cas être utilisées.

Réentrance

performBlock est toujours asynchrone, et n'est donc pas réentrant. Eh bien, certains peuvent le considérer comme réentrant, en ce sens que vous pouvez l'appeler à partir d'un bloc qui a été appelé avec performBlock. Cependant, si vous faites cela, tous les appels à performBlock reviendront immédiatement et le bloc ne s'exécutera pas avant qu'au moins le bloc d'exécution actuellement ait complètement terminé son travail.

[moc performBlock:^{
    doSomething();
    [moc performBlock:^{
      doSomethingElse();
    }];
    doSomeMore();
}];

Ces fonctions seront toujours exécutées dans cet ordre:

doSomething()
doSomeMore()
doSomethingElse()

performBlockAndWait est toujours synchrone. De plus, il est également réentrant. Les appels multiples ne bloquent pas. Ainsi, si vous finissez par appeler performBlockAndWait alors que vous êtes à l'intérieur d'un bloc qui était exécuté à la suite d'un autre performBlockAndWait, alors c'est OK. Vous obtiendrez le comportement attendu, dans la mesure où le deuxième appel (et tout appel ultérieur) ne provoquera pas de blocage. De plus, le second s'exécutera complètement avant son retour, comme vous vous en doutez.

[moc performBlockAndWait:^{
    doSomething();
    [moc performBlockAndWait:^{
      doSomethingElse();
    }];
    doSomeMore();
}];

Ces fonctions seront toujours exécutées dans cet ordre:

doSomething()
doSomethingElse()
doSomeMore()

FIFO

FIFO signifie "First In First Out", ce qui signifie que les blocs seront exécutés dans l'ordre dans lequel ils ont été placés dans la file d'attente interne.

performBlock respecte toujours la structure FIFO de la file d'attente interne. Chaque bloc sera inséré dans la file d'attente et ne s'exécutera que lorsqu'il sera supprimé, dans FIFO commande.

Par définition, performBlockAndWait casse FIFO classement car il saute la file d'attente des blocs qui ont déjà été mis en file d'attente.

Les blocs soumis avec performBlockAndWait n'ont pas à attendre les autres blocs en cours d'exécution dans la file d'attente. Il y a plusieurs façons de voir cela. Un simple est celui-ci.

[moc performBlock:^{
    doSomething();
    [moc performBlock:^{
      doSomethingElse();
    }];
    doSomeMore();
    [moc performBlockAndWait:^{
      doSomethingAfterDoSomethingElse();
    }];
    doTheLastThing();
}];

Ces fonctions seront toujours exécutées dans cet ordre:

doSomething()
doSomeMore()
doSomethingAfterDoSomethingElse()
doTheLastThing()
doSomethingElse()

C'est évident dans cet exemple, c'est pourquoi je l'ai utilisé. Considérez, cependant, si votre MOC reçoit des informations à partir de plusieurs endroits. Cela pourrait être un peu déroutant.

Cependant, le point à retenir est que performBlockAndWait est préemptif et peut sauter la file d'attente FIFO.

Impasse

Vous n'aurez jamais de blocage appelant performBlock. Si vous faites quelque chose de stupide à l'intérieur du bloc, vous risquez de bloquer, mais appeler performBlock ne bloquera jamais. Vous pouvez l'appeler de n'importe où et il ajoutera simplement le bloc à la file d'attente et l'exécutera dans le futur.

Vous pouvez facilement obtenir des interblocages appelant performBlockAndWait, surtout si vous l'appelez à partir d'une méthode qu'une entité externe peut appeler ou sans discernement dans des contextes imbriqués. Plus précisément, vous êtes presque garanti de bloquer vos applications si un parent appelle performBlockAndWait sur un enfant.

Événements utilisateur

Core Data considère un "événement utilisateur" comme n'importe quoi entre les appels à processPendingChanges. Vous pouvez lire les détails de pourquoi cette méthode est importante, mais ce qui se passe dans un "événement utilisateur" a des implications sur les notifications, la gestion des annulations, la suppression de la propagation, la modification de la fusion, etc.

performBlock encapsule un "événement utilisateur" ce qui signifie que le bloc de code est automatiquement exécuté entre des appels distincts à processPendingChanges.

performBlockAndWait n'encapsule pas un "événement utilisateur". Si vous souhaitez que le bloc soit traité comme un événement utilisateur distinct, vous devez le faire vous-même.

Pool de libération automatique

performBlock enveloppe le bloc dans son propre pool d'autorelease.

performBlockAdWait ne fournit pas de pool d'autorelease unique. Si vous en avez besoin, vous devez le fournir vous-même.

Opinions personnelles

Personnellement, je ne pense pas qu'il y ait de très bonnes raisons d'utiliser performBlockAndWait. Je suis sûr que quelqu'un a un cas d'utilisation qui ne peut être accompli autrement, mais je ne l'ai pas encore vu. Si quelqu'un connaît ce cas d'utilisation, veuillez le partager avec moi.

Le plus proche appelle performBlockAndWait sur un contexte parent (ne faites jamais cela sur un MOC NSMainConcurrencyType car cela pourrait verrouiller votre interface utilisateur). Par exemple, si vous souhaitez vous assurer que la base de données a été entièrement enregistrée sur le disque avant que le bloc actuel ne revienne et que d'autres blocs aient une chance de s'exécuter.

Ainsi, il y a quelque temps, j'ai décidé de traiter Core Data comme une API complètement asynchrone. Par conséquent, j'ai beaucoup de code de données de base et je n'ai pas un seul appel à performBlockAndWait, en dehors des tests.

La vie est bien meilleure de cette façon. J'ai beaucoup moins de problèmes que je n'en avais quand je pensais "ça doit être utile sinon ils ne l'auraient pas fourni".

Maintenant, je n'ai tout simplement plus besoin de performBlockAndWait. Du coup, peut-être que ça en a changé, et je l'ai raté car ça ne m'intéresse plus ... mais j'en doute.

69
Jody Hagins