I'm using a subclass of UIManagedDocument
to use Core Data in my project. The point is for the subclass to return a singleton instance so that my screens can simply call it and the managed object context remains the same for all of them.
Before using the UIManagedDocument
, I need to prepare it by opening it if its file path already exists, or creating it if it doesn't yet. I created a convenience method prepareWithCompletionHandler:
in the subclass to facilitate both scenarios.
@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
What I'm trying to do is call this preparation method in my app delegate's didFinishLaunchingWithOptions
and wait for the completion block to be executed BEFORE returning either YES
or NO
at the end. My current approach doesn't work.
- (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;
}
How can I wait until the completion handler in prepareWithCompletionHandler
is called before returning successful
? I'm really confused.
I'm unsure why the didFinishLaunching
return status is dependent upon the success of your completion handler as you're not apparently even considering launchOptions
. I'd hate to see you put an synchronous call (or more accurately, use a semaphore to convert an asynchronous method into a synchronous one) here, as it will slow down the app and, if its slow enough, you risk being killed by the watch dog process.
Semaphores are one common technique for making an asynchronous process synchronous:
- (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;
}
But, upon further review of what prepareWithCompletionHandler
is doing, it's apparently calling methods that dispatch their own completion blocks to the main queue, so any attempts to make this synchronous will deadlock.
So, use asynchronous patterns. If you want to initiate this in the didFinishLaunchingWithOptions
, you can have it post a 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;
}
And you can then have your view controller addObserverForName
to observe this notification.
Alternatively, you can move this code out of the app delegate and into that view controller, eliminating the need for the notification.