【问题标题】:Wait for async task to finish completion block before returning in app delegate等待异步任务完成完成块,然后在应用委托中返回
【发布时间】:2017-11-18 18:30:51
【问题描述】:

我正在使用UIManagedDocument 的子类在我的项目中使用Core Data。重点是让子类返回一个单例实例,以便我的屏幕可以简单地调用它,并且托管对象上下文对所有这些都保持相同。

在使用UIManagedDocument 之前,如果它的文件路径已经存在,我需要通过打开它来准备它,或者如果它还不存在则创建它。我在子类中创建了一个方便的方法prepareWithCompletionHandler: 来方便这两种情况。

@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

我要做的是在我的应用程序委托的didFinishLaunchingWithOptions 中调用此准备方法,并等待在最后返回YES 或NO 之前执行完成块。我目前的方法行不通。

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

如何等到prepareWithCompletionHandler 中的完成处理程序被调用后再返回successful?我真的很困惑。

【问题讨论】:

  • 另外,您为什么希望等待准备文件完成?您不使用launchOptions,也不需要向IOS报告是否处理了您的openURL请求。使用- (void)applicationDidFinishLaunching:(UIApplication *)application,不要等待完成。
  • dispatch_group_notify 不起作用,因为它只是安排一个块在组上的所有先前操作完成时执行,然后立即返回。 dispatch_group_wait 会如你所愿。

标签: ios objective-c grand-central-dispatch


【解决方案1】:

我不确定为什么didFinishLaunching 返回状态取决于您的完成处理程序的成功,因为您显然甚至没有考虑launchOptions。我不想看到你在这里放一个同步调用(或者更准确地说,使用信号量将异步方法转换为同步方法),因为它会减慢应用程序的速度,如果它足够慢,你就有被杀死的风险看门狗进程。

信号量是使异步进程同步的一种常用技术:

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

但是,在进一步查看 prepareWithCompletionHandler 所做的事情后,它显然调用了将自己的完成块分派到主队列的方法,因此任何使这种同步的尝试都会死锁。

所以,使用异步模式。如果您想在didFinishLaunchingWithOptions 中发起此操作,您可以让它发布通知:

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

然后您可以让您的视图控制器addObserverForName 观察此通知。

或者,您可以将此代码移出应用委托并移入该视图控制器,从而无需通知。

【讨论】:

  • 我也不确定——我应该把它放回YES。但关键是文档在显示应用程序的 UI 之前就已经准备好了。还是应该在第一个视图控制器中这样做?
  • @MattQuiros 我可以在这里开始异步“准备”过程,但我不会等待它,而是我可能会让完成块发布本地通知,并拥有第一个视图控制器观察该通知,在通知到来之前不启动取决于该进程的步骤。
  • ^很好的提示。顺便说一句,我刚刚尝试了您的解决方案,它导致了僵局。 :( 实际上,我什至在调度组之前就尝试过信号量。
  • 如果它导致死锁,那么你不需要信号量,你的方法 prepareWithCompletionHandler 是同步的。
  • @MattQuiros 是的,那意味着你不能同步运行它(你不能阻塞主队列,等待一些本身将在主队列上运行的东西)。对于我们之前的讨论,无论如何,您可能不想这样做。去异步!
【解决方案2】:

这里有很多建议的解决方案使用dispatch_group_wait 或信号量,但真正的解决方案是重新考虑为什么要阻止返回didFinishLaunching,直到可能冗长的异步请求完成之后。如果在操作完成之前你真的不能做任何其他有用的事情,我的建议是在初始化发生时显示某种加载请等待屏幕,然后立即从 didFinishLaunching 返回。

【讨论】:

    【解决方案3】:

    对于您使用调度组的情况会略有不同:

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

    【讨论】:

    • 也会导致死锁。 :(
    • @MattQuiros,我已经编辑了帖子,应该可以解决死锁问题。
    • 谢谢,但我担心每当openWithCompletionHandler: 或saveToURL: 在prepareWithCompletionHandler: 中调用他们的回调方法时,死锁仍然会发生。正如@Rob 所说,我不能阻塞主队列,我认为 open 和 create 方法,是异步的,正在内部调度回主队列以执行它们的完成块。我可能应该找到另一种解决方法。
    • 等等,不,它们应该被分派回调用prepareWithCompletionHandler的线程,对吧?这变得更加令人困惑。除非我创建自己的队列...
    • @MattQuiros,出现死锁,因为open 和save 在串行队列中执行,并且在同一个串行队列中等待完成。如果您在其他并发队列中调用它们,那么一切都会好起来的。
    猜你喜欢
    • 2013-03-22
    • 2016-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多