NSInvocationOperation回调过快



我知道类似的问题已经被问过几次了,但我很难理解如何解决这个特定的问题。到目前为止,我所做的一切都是在主踏板上进行的。我现在发现我需要执行一项需要一些时间的操作,我想在操作期间在显示器上添加一个HUD,并在操作完成后将其淡出。

在阅读了很多关于GCD的内容(并感到非常困惑)后,我决定最简单的方法是用NSInvocationOperation调用耗时的方法,并将其添加到新创建的NSOperationQueue中。这就是我所拥有的:

[self showLoadingConfirmation]; // puts HUD on screen
// this bit takes a while to draw a large number of dots on a MKMapView            
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
          selector:@selector(timeConsumingOperation:)
            object:[self lotsOfDataFromManagedObject]];
// this fades the HUD away and removes it from the superview
[operation setCompletionBlock:^{ [self performSelectorOnMainThread:@selector(fadeConfirmation:) withObject:loadingView waitUntilDone:YES]; }];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperation:operation];

我希望这能显示HUD,开始在地图上画点,然后操作完成后,淡出HUD。

相反,它显示HUD,开始在地图上绘制点,并在绘制点的同时淡出HUD。根据我的NSLogs,在调用淡化HUD的方法之前,大约有四分之一秒的延迟。与此同时,点的绘制又继续了几秒钟。

我该怎么做才能让它等到地图上的绘图完成后再淡出HUD?

感谢

编辑后添加:

在做出以下改变后,我几乎取得了成功:

NSInvocationOperation *showHud = [[NSInvocationOperation alloc] initWithTarget:self
        selector:@selector(showLoadingConfirmation)
          object:nil];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
          selector:@selector(timeConsumingOperation:)
            object:[self lotsOfDataFromManagedObject]];
NSInvocationOperation *hideHud = [[NSInvocationOperation alloc] initWithTarget:self
        selector:@selector(fadeConfirmation:)
          object:loadingView];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSArray *operations = [NSArray arrayWithObjects:showHud, operation, hideHud, nil];
[operationQueue addOperations:operations waitUntilFinished:YES];

奇怪的是,它似乎首先调用timeConsumingOperation,然后显示LoadingConfirmation,然后显示fadeConfirmation。这是根据我的NSLogs,它们是在这些方法中激发的。

我在屏幕上看到的行为是:画出点,地图相应地调整缩放(时间消耗操作的一部分),然后HUD出现在屏幕上,然后什么都没有。所有三个NSLogs都会立即出现,即使showLoadingConfirmation在timeConsumingOperation完成之前不会发生,fadeConfirmation似乎根本不会发生。

这看起来很奇怪,但似乎也表明有一种方法可以在timeConsumingOperation完成时发生一些事情。

我试着添加这个:

[operationQueue setMaxConcurrentOperationCount:1];

还有这个:

[showHud setQueuePriority:NSOperationQueuePriorityVeryHigh];
[operation setQueuePriority:NSOperationQueuePriorityNormal];
[hideHud setQueuePriority:NSOperationQueuePriorityVeryLow];

但它们似乎没有什么区别。

在为NSInvocationOperation设置完成处理程序时,请注意您实际在做什么:当此类操作完成时,会发生以下情况(来自NSOperation类参考):

不能保证完成块的确切执行上下文,但通常是辅助线程。因此,你应该不要使用此块来执行任何需要非常特定的执行上下文。相反,你应该把这项工作转移到你的应用程序的主线程或能够例如,如果您有一个用于协调的自定义线程操作完成后,可以使用completion块ping那个线程。

因此,首先,块在辅助线程上执行(因此它将进入该线程的队列,当轮到它时,它只会向主线程的队列发送另一个作业)。这意味着它将与其他挂起的作业混合在这样的队列中,比如从timeConsumingOperation:选择器发送到主队列的管脚的最后更新。

问题来了:如果你不为不同的作业设置优先级,就无法确定这些作业最终处理的顺序,即使你知道它们以前是及时发送的。此外,在您的情况下,NSInvocationOperation已完成并不一定意味着在调用块时所有对象都已绘制在屏幕上,这只意味着它们已被发送到UI更新线程,以便在轮到它们时进行处理。

考虑到这一点,考虑到你不想走GCD的路,(我建议再尝试一次,因为我知道一开始这并不容易,但当你开始使用它时,你会意识到这是一个很好的解决方案,几乎适用于你想在iPhone上做的所有多线程工作)我会创建一个NSOperationQueue,并将所有作业发送到那里(其中包括删除HUD的作业,但优先级低于其他作业)。通过这种方式,您可以确保在完成所有"pin"工作后,在主队列中处理HUD的删除,这是您的第一个目标。

如果HUD正在消失,则调用了您的完成块,这意味着返回了timeConsumingOperation:方法。

如果您仍然看到正在绘制的点,这意味着即使在timeConsumingOperation:返回之后,仍有动画或绘制事务在进行中或仍在排队。解决方案取决于您正在使用的绘图技术。你在使用核心动画吗?带注释的MapKit?

最新更新