5

我知道类似的问题已经被问过几次,但我很难弄清楚如何解决这个特定的问题。到目前为止,我所做的一切都是在主踏板上进行的。我现在发现我需要执行一个需要一些时间的操作,并且我想在操作期间在我的显示器上添加一个 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,再调用showLoadingConfirmation,再调用fadeConfirmation。这是根据我在这些方法中触发的 NSLogs 得出的。

我在屏幕上看到的行为是这样的:绘制点并且地图相应地调整它的缩放(部分时间ConsumingOperation),然后HUD出现在屏幕上,然后什么都没有。所有三个 NSLog 都会立即出现,即使 showLoadingConfirmation 在 timeConsumingOperation 完成之前不会发生,并且 fadeConfirmation 似乎根本没有发生。

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

我试着添加这个:

[operationQueue setMaxConcurrentOperationCount:1];

还有这个:

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

但它们似乎没有任何区别。

4

2 回答 2

1

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

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

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

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

考虑到这一点,并且考虑到您不想采用 GCD 方式,(我建议再试一次,因为我知道一开始并不容易,但是当您开始使用它时,您会意识到这是一个很棒的解决方案对于您想在 iPhone 上执行的几乎所有多线程操作)我将创建一个 NSOperationQueue 并将所有作业发送到那里(删除包含的 HUD 的作业,但优先级低于其他作业)。这样,您可以确保在完成所有“固定”作业后,在主队列中处理 HUD 的移除,这是您的第一个目标。

于 2012-11-19T14:40:01.523 回答
1

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

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

于 2012-11-19T14:50:41.820 回答