Je dois effectuer une série d'opérations de téléchargement et d'écriture de base de données dans mon application. J'utilise NSOperation
et NSOperationQueue
pour la même chose.
C'est un scénario d'application:
Comme dit, j'ai défini un NSOperation
pour chaque tâche. Dans le premier cas (Task1), j'envoie une demande au serveur pour récupérer tous les codes postaux. Le délégué au sein de la NSOperation
recevra les données. Ces données sont ensuite écrites dans la base de données. L'opération de base de données est définie dans une classe différente. De NSOperation
class J'appelle la fonction write définie dans la classe database.
Ma question est de savoir si l'opération d'écriture de base de données se produit dans le thread principal ou dans un thread d'arrière-plan? Comme je l'appelais dans un NSOperation
, je m'attendais à ce qu'il s'exécute dans un autre thread (pas MainThread) que le NSOperation
. Quelqu'un peut-il s'il vous plaît expliquer ce scénario lorsqu'il s'agit de NSOperation
et NSOperationQueue
.
Ma question est de savoir si l'opération d'écriture de base de données se produit dans le thread principal ou dans un thread d'arrière-plan?
Si vous créez un NSOperationQueue
à partir de zéro comme dans:
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
Ce sera dans un fil de fond:
Les files d'opérations fournissent généralement les threads utilisés pour exécuter leurs opérations. Dans OS X version 10.6 et ultérieure, les files d’opérations utilisent la bibliothèque libdispatch (également connue sous le nom de Grand Central Dispatch) pour lancer l’exécution de leurs opérations. En conséquence, les opérations sont toujours exécutées sur un thread séparé , qu'elles soient désignées comme opérations simultanées ou non.
Sauf si vous utilisez le mainQueue
:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
Vous pouvez également voir le code comme ceci:
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
[myQueue addOperationWithBlock:^{
// Background work
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Main thread work (UI usually)
}];
}];
Et la version de GCD:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
{
// Background work
dispatch_async(dispatch_get_main_queue(), ^(void)
{
// Main thread work (UI usually)
});
});
NSOperationQueue
donne un contrôle plus précis de ce que vous voulez faire. Vous pouvez créer des dépendances entre les deux opérations (télécharger et enregistrer dans la base de données). Pour transmettre les données d'un bloc à l'autre, vous pouvez par exemple supposer qu'un NSData
viendra du serveur de sorte que:
__block NSData *dataFromServer = nil;
NSBlockOperation *downloadOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakDownloadOperation = downloadOperation;
[weakDownloadOperation addExecutionBlock:^{
// Download your stuff
// Finally put it on the right place:
dataFromServer = ....
}];
NSBlockOperation *saveToDataBaseOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakSaveToDataBaseOperation = saveToDataBaseOperation;
[weakSaveToDataBaseOperation addExecutionBlock:^{
// Work with your NSData instance
// Save your stuff
}];
[saveToDataBaseOperation addDependency:downloadOperation];
[myQueue addOperation:saveToDataBaseOperation];
[myQueue addOperation:downloadOperation];
Edit: Pourquoi j'utilise __weak
référence pour les opérations, peut être trouvé ici . Mais en un mot, il faut éviter de retenir les cycles.
Si vous souhaitez effectuer l'opération d'écriture de base de données dans le thread d'arrière-plan, vous devez créer un NSManagedObjectContext
pour ce thread.
Vous pouvez créer l’arrière-plan NSManagedObjectContext
dans la méthode de démarrage de votre sous-classe NSOperation
correspondante.
Vérifiez les docs Apple pour accès simultané avec les données de base.
Vous pouvez également créer un NSManagedObjectContext
qui exécute les demandes dans son propre thread d'arrière-plan en le créant avec NSPrivateQueueConcurrencyType
et en effectuant les demandes dans son performBlock:
méthode.
Dans iOS 4 et versions ultérieures, les files d'attente d'opérations utilisent Grand Central Dispatch pour exécuter des opérations. Avant iOS 4, ils créaient des threads distincts pour les opérations non simultanées et lançaient des opérations simultanées à partir du thread actuel.
Alors,
[NSOperationQueue mainQueue] // added operations execute on main thread
[NSOperationQueue new] // post-iOS4, guaranteed to be not the main thread
Dans votre cas, vous pouvez créer votre propre "thread de base de données" en sous-classant NSThread
et en lui envoyant des messages avec performSelector:onThread:
.
Le thread d'exécution de NSOperation dépend du NSOperationQueue
où vous avez ajouté l'opération. Regardez cette déclaration dans votre code -
[[NSOperationQueue mainQueue] addOperation:yourOperation]; // or any other similar add method of NSOperationQueue class
Tout cela suppose que vous n’ayez plus fait de thread dans la méthode main
de NSOperation
, qui est le monstre réel dans lequel les instructions de travail que vous avez écrites (devraient être écrites).
Cependant, en cas d'opérations simultanées, le scénario est différent. La file d'attente peut générer un thread pour chaque opération simultanée. Bien que ce ne soit pas garanti et que cela dépend des ressources système par rapport aux demandes en ressources opérationnelles à ce stade du système. Vous pouvez contrôler la simultanéité de la file d’opérations par sa propriété maxConcurrentOperationCount
.
EDIT -
J'ai trouvé votre question intéressante et j'ai moi-même effectué quelques analyses/journalisation. J'ai NSOperationQueue créé sur le fil principal comme ceci -
self.queueSendMessageOperation = [[[NSOperationQueue alloc] init] autorelease];
NSLog(@"Operation queue creation. current thread = %@ \n main thread = %@", [NSThread currentThread], [NSThread mainThread]);
self.queueSendMessageOperation.maxConcurrentOperationCount = 1; // restrict concurrency
Et puis, j'ai créé un NSOperation et je l'ai ajouté à l'aide de addOperation. Dans la méthode principale de cette opération quand j'ai vérifié le fil actuel,
NSLog(@"Operation obj = %@\n current thread = %@ \n main thread = %@", self, [NSThread currentThread], [NSThread mainThread]);
ce n'était pas comme fil conducteur. Et, trouvé que l'objet de fil actuel n'est pas l'objet de fil principal.
Ainsi, la création personnalisée d'une file d'attente sur le thread principal (sans concurrence entre ses opérations) ne signifie pas nécessairement que les opérations seront exécutées en série sur le thread principal lui-même.
Le résumé de la documentation est operations are always executed on a separate thread
(post iOS 4 implique les files d’opérations sous-jacentes à GCD).
Il est trivial de vérifier qu'il fonctionne bien sur un thread non principal:
NSLog(@"main thread? %@", [NSThread isMainThread] ? @"YES" : @"NO");
Lors de l'exécution dans un thread, il est simple d'utiliser GCD/libdispatch pour exécuter quelque chose sur le thread principal, qu'il s'agisse de données de base, d'interface utilisateur ou d'un autre code requis pour s'exécuter sur le thread principal:
dispatch_async(dispatch_get_main_queue(), ^{
// this is now running on the main thread
});