我目前正在制作练习应用程序,并且正在使用GCD,并且在理解某些方面时遇到了一些麻烦。
我在这段代码中有一个问题:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
TWPhotoCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
//cell.backgroundColor = [UIColor clearColor];
dispatch_queue_t filterQueue = dispatch_queue_create("filter queue", NULL);
dispatch_async(filterQueue, ^{
UIImage *filterImage = [self filteredImageFromImage:self.photo.image usingFilter:[self.filters objectAtIndex:indexPath.row]];
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = filterImage;
});
});
return cell;
}
当你调用dispatch_async时,它基本上切换到我声明的队列,换句话说就是单独线程上的filterQueue?此外,我在第一个dispatch_async中调用dispatch_async返回主队列以进行一些UI更改。是否在我最初创建的自定义filterQueue的线程上调用了第二个dispatch_async(我切换到主队列的位置)?
答案 0 :(得分:1)
首先,您需要移动在此方法之外创建调度队列的行。
您可能只应在程序生命周期内创建一次调度队列,或者至少在视图控制器的生命周期内创建一次。
您可以使filterQueue成为视图控制器的实例变量,然后将创建队列的代码移动到viewDidLoad方法。
然后你需要在视图控制器中添加一个dealloc方法,并在你的调度队列上调用dispatch_release。
现在关于您的问题:
dispatch_async提交一个代码块以便在调度队列上进行处理。在这种情况下,您使用dispatch_queue_create()创建了目标队列。如果您阅读该函数的文档,他们会说如果为第二个参数传入NULL,则会得到一个串行队列。串行队列只能按FIFO顺序一次运行1个任务。
所以是的,您要求您的代码块在串行队列中运行,该队列在后台线程上运行。
该代码调用您的方法filteredImageFromImage
,您需要确保该方法是线程安全的,并且不使用/设置self的任何实例变量。
完成对filteredImageFromImage
的通话后,您可以拨打dispatch_async(dispatch_get_main_queue())
。这说"从我的后台线程中,提交一个代码块以在主队列上运行(在主线程上运行。)"
您必须这样做,因为您无法从后台线程更改UI对象。 UIKit几乎没有线程安全。如果代码对视图进行了更改,则需要在主线程上完成。
考虑将dispatch_async与主队列以外的队列一起调用,要求助手为您做一些工作,同时继续做自己的事情。助理在后台线程上运行。
您要求助理在工作完成时通知您,以便您可以将其安装在视图中。这就是对dispatch_async(dispatch_get_main_queue())
的调用。它有后台线程在主线程上调用代码。
请注意,您的代码存在编写的潜在问题。
如果对filteredImageFromImage
的调用仍在运行且用户在屏幕外滚动此单元格,则单元格将被回收以在集合视图中显示不同的条目。但是,当filteredImageFromImage
完成时,生成的图像将被安装到" cell"中,它被回收并且现在在不同的indexPath上显示数据。
相反,您应该询问指定索引路径中单元格的集合视图,如果它仍然可用,那么您应该安装该图像:
dispatch_async(filterQueue, ^{
UIImage *filterImage = [self filteredImageFromImage:self.photo.image usingFilter:[self.filters objectAtIndex:indexPath.row]];
dispatch_async(dispatch_get_main_queue(), ^
{
//Ask the collection view for the target cell
UICollectionViewCell targetCell = [collectionView cellForItemAtIndexPath: indexPath];
if (targetCell != nil)
targetCell.imageView.image = filterImage;
}
);
});
请注意,创建自己的队列并不常见。使用现有调度队列之一是更常见,更容易且通常更有效地使用系统资源。您可以使用函数`dispatch_get_global_queue()来获取现有队列。您可以使用dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT)或DISPATCH_QUEUE_PRIORITY_HIGH。但是,使用DISPATCH_QUEUE_PRIORITY_HIGH可能会导致设备响应性降低,因此应谨慎使用。
答案 1 :(得分:1)
Dispatch Queues是一种抽象类型,它脱离了线程模型。您对Dispatch Queues的所有了解都是他们可以派遣任务。这些任务与其他队列中的任务并行运行。
当你调用dispatch_async时,它基本上会切换到我声明的队列,换句话说就是单独一个线程上的filterQueue?
除了与filterQueue
上发送的任务并行运行之外,dispatch_get_main_queue()
上发送的任务不能得到任何保证。
我最初创建的自定义filterQueue的线程是否调用了第二个dispatch_async(我切换到主队列的位置)?
除了与dispatch_get_main_queue()
上发送的任务并行运行之外,filterQueue
上发送的任务不能得到任何保证。
现在有了所有的API保证,让我们进入当前的实现。
主队列上运行的所有任务都在一个线程中:主线程。创建filterQueue
时,会为其分配一个线程池。任何这些线程都可以运行分派到filterQueue
的任务。注意:此线程池将从不包含主线程。
现在两个问题的答案应该是显而易见的。在filterQueue
上调度的任务在某个不是主线程的线程上运行。在dispatch_get_main_queue()
上调度的任务在主线程上运行。