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.
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.
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;
}
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.