web-dev-qa-db-fra.com

Attendez que la tâche asynchrone termine le bloc d'achèvement avant de retourner dans le délégué d'application

J'utilise une sous-classe de UIManagedDocument pour utiliser Core Data dans mon projet. Le fait est que la sous-classe retourne une instance singleton afin que mes écrans puissent simplement l'appeler et que le contexte de l'objet géré reste le même pour tous.

Avant d'utiliser le UIManagedDocument, je dois le préparer en l'ouvrant si son chemin de fichier existe déjà, ou en le créant s'il ne l'est pas encore. J'ai créé une méthode pratique prepareWithCompletionHandler: dans la sous-classe pour faciliter les deux scénarios.

@implementation SPRManagedDocument

// Singleton class method here. Then...

- (void)prepareWithCompletionHandler:(void (^)(BOOL))completionHandler
{
    __block BOOL successful;

    // _exists simply checks if the document exists at the given file path.
    if (self.exists) {
        [self openWithCompletionHandler:^(BOOL success) {
            successful = success;

            if (success) {
                if (self.documentState != UIDocumentStateNormal) {
                    successful = NO;
                }
            }
            completionHandler(successful);
        }];
    } else {
        [self saveToURL:self.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
            successful = success;

            if (success) {
                if (self.documentState != UIDocumentStateNormal) {
                    successful = NO;
                }
            }
            completionHandler(successful);
        }];
    }
}

@end

Ce que j'essaie de faire, c'est d'appeler cette méthode de préparation dans le didFinishLaunchingWithOptions de mon délégué d'application et d'attendre que le bloc d'achèvement soit exécuté AVANT de renvoyer YES ou NO à la fin . Mon approche actuelle ne fonctionne pas.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    __block BOOL successful;
    SPRManagedDocument *document = [SPRManagedDocument sharedDocument];

    dispatch_group_t group = dispatch_group_create();

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [document prepareWithCompletionHandler:^(BOOL success) {
            successful = success;
        }];
    });

    dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    });

    return successful;
}

Comment puis-je attendre que le gestionnaire de complétion dans prepareWithCompletionHandler soit appelé avant de retourner successful? Je suis vraiment confus.

34
Matthew Quiros

Je ne sais pas pourquoi le statut de retour de didFinishLaunching dépend du succès de votre gestionnaire d'achèvement car vous ne pensez même pas à launchOptions. Je détesterais que vous mettiez un appel synchrone (ou plus précisément, utilisez un sémaphore pour convertir une méthode asynchrone en une méthode synchrone) ici, car cela ralentira l'application et, si c'est assez lent, vous risquez d'être tué par le processus de chien de garde.

Les sémaphores sont une technique courante pour rendre un processus asynchrone synchrone:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    __block BOOL successful;
    SPRManagedDocument *document = [SPRManagedDocument sharedDocument];

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    [document prepareWithCompletionHandler:^(BOOL success) {
        successful = success;
        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return successful;
}

Mais, après un examen plus approfondi de ce que fait prepareWithCompletionHandler, il appelle apparemment des méthodes qui envoient leurs propres blocs d'achèvement à la file d'attente principale, donc toute tentative de rendre ce synchrone se bloquera.

Utilisez donc des modèles asynchrones. Si vous voulez lancer ceci dans le didFinishLaunchingWithOptions, vous pouvez lui faire poster une notification:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    __block BOOL successful;
    SPRManagedDocument *document = [SPRManagedDocument sharedDocument];

    [document prepareWithCompletionHandler:^(BOOL success) {
        successful = success;
        [[NSNotificationCenter defaultCenter] postNotificationName:kDocumentPrepared object:nil];
    }];

    return successful;
}

Et vous pouvez alors avoir votre contrôleur de vue addObserverForName pour observer cette notification.

Vous pouvez également déplacer ce code hors du délégué d'application et dans ce contrôleur de vue, éliminant ainsi le besoin de notification.

44
Rob

Pour votre cas, l'utilisation du groupe de répartition sera légèrement différente:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    __block BOOL successful;
    SPRManagedDocument *document = [SPRManagedDocument sharedDocument];

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [document prepareWithCompletionHandler:^(BOOL success) {
            successful = success;
            dispatch_group_leave(group);
        }];
    }];

    dispatch_group_wait(group,  DISPATCH_TIME_FOREVER);
    return successful;
}
5
Cy-4AH

Beaucoup de solutions proposées ici en utilisant soit dispatch_group_wait ou des sémaphores, mais la vraie solution est de repenser pourquoi vous voulez empêcher le retour de didFinishLaunching jusqu'à ce qu'une requête asynchrone éventuellement longue soit terminée. Si vous ne pouvez vraiment rien faire d'autre avant la fin de l'opération, ma recommandation serait d'afficher une sorte de chargement, attendez l'écran pendant l'initialisation, puis revenez immédiatement de didFinishLaunching.

4
David Berry