Reputation: 55
I am currently making a practice app and was using GCD in it and was having a little big of trouble understanding certain aspects.
I have a question in this code:
- (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;
}
When you call dispatch_async does that basically switch to the queue I declared or in other words the filterQueue on a separate thread? Also I am calling dispatch_async inside the first dispatch_async to go back to the main queue in order to make some UI changes. Does the second dispatch_async (where I switch to the main queue) get called on the thread of the custom filterQueue I originally created?
Upvotes: 0
Views: 109
Reputation: 131491
First of all, you need to move the line that creates the dispatch queue outside of this method.
You should probably only create your dispatch queue once during the life of your program, or at the least only once for the life of your view controller.
You could make filterQueue an instance variable of your view controller and then move the code that creates the queue to your viewDidLoad method.
Then you need to add a dealloc method to your view controller and call dispatch_release on your dispatch queue.
Now as to your questions:
dispatch_async submits a block of code for processing on a dispatch queue. In this case you created the target queue using dispatch_queue_create(). If you read the docs for that function they say that if you pass in NULL for the second parameter, you get a serial queue. A serial queue will only run 1 task at a time in FIFO order.
So yes, you are asking that your block of code be run in your serial queue, which runs on a background thread.
That code calls your method filteredImageFromImage
, which you will need to make sure is thread-safe, and doesn't use/set any instance variables of self.
Once the call to filteredImageFromImage
is complete, you use a call to dispatch_async(dispatch_get_main_queue())
. This says "From my background thread, submit a block of code to run on the main queue (which runs on the main thread.)"
You have to do this because you can't change UI objects from a background thread. Almost NONE of UIKit is thread-safe. If the code makes a change to a view, it needs to be done on the main thread.
Think of calling dispatch_async with queue other than the main queue as asking an assistant to do some work for you, at the same time that you continue to do your own thing. The assistant runs on a background thread.
You ask the assistant to let you know when the work is done so that you can install it in your view. That's what the call to dispatch_async(dispatch_get_main_queue())
does. It has the background thread invoke code on the main thread.
Note that your code has a potential problem as written.
If the call to filteredImageFromImage
is still running and the user scrolls this cell off-screen, the cell will be recycled to display a different entry in your collection view. However, when filteredImageFromImage
finishes, the resulting image will be installed into "cell", which was recycled and is now displaying data at a different indexPath.
Instead, you should ask the collection view for the cell at the specified index path, and if it is still available, THEN you should install the image:
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;
}
);
});
Note that creating your own queue is not that common. It's more common, easier, and generally more efficient use of system resources, to use one of the existing dispatch queues. You can use the function `dispatch_get_global_queue() to get an existing queue. You might use dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT) or DISPATCH_QUEUE_PRIORITY_HIGH. Using DISPATCH_QUEUE_PRIORITY_HIGH may cause the device to be less responsive, however, so it should be used with care.
Upvotes: 1
Reputation: 42598
Dispatch Queues are an abstract type the breaks away from a threading model. All you know about Dispatch Queues are that they can dispatch tasks. These tasks run in parallel with tasks from other queues.
When you call dispatch_async does that basically switch to the queue I declared or in other words the filterQueue on a separate thread?
You cannot be guaranteed anything about tasks dispatched on filterQueue
other than it runs in parallel with tasks dispatched on dispatch_get_main_queue()
.
Does the second dispatch_async (where I switch to the main queue) get called on the thread of the custom filterQueue I originally created?
You cannot be guaranteed anything about tasks dispatched on dispatch_get_main_queue()
other than it runs in parallel with tasks dispatched on filterQueue
.
Now with all the API guarantees out of the way, lets get to the current implementation.
All tasks run on the main queue are in a single thread: the main thread. When filterQueue
is created, it is assigned a pool of threads. Any of these threads can run a task dispatched to filterQueue
. Note: this pool of threads will never include the main thread.
Now the answer to both questions should be obvious. The task dispatched on filterQueue
is run on some thread which is not the main thread. The task dispatched on dispatch_get_main_queue()
is run on the main thread.
Upvotes: 1