iOS - Async Drawing - Part 2

Stephen Zaharuk / Tuesday, December 15, 2015

In the world of App Development, performance is always a concern. Today, i'm going to show you how you can easily add performance to your application for custom drawing.

If you've ever used the CoreGraphics API's, then you probably know that the drawing work is done mainly on the CPU. Since, the rest of your Application logic is also going to be dependent on the CPU, you don't want your UI code to be locking it up. 

In this three part series I'm going to walk you through the code to set this up. 

Part 1: Drawing into an Image

Part 2: Using GCD

Part 3: Putting them together

Part 2: Using GCD

For the Async code, we're going to use the GCD (Grand Central Dispatch). And to make our code clean we're going to setup so a couple of rules. 

1. Only one drawing operation can ever occur at a single time

2. If multiple requests come in, while we're drawing, we only use the last one to come in, and throw out the others. 

So here is the code:

@interface SingleDispatchQueue()
{
    dispatch_queue_t _masterQueue;
    NSInteger _currentlyRunningBlocks;
    dispatch_block_t _queuedBlock;
}

@end

@implementation SingleDispatchQueue

-(id)init
{
    self = [super init];
    if(self)
    {
        _masterQueue = dispatch_queue_create("masterQueue", DISPATCH_QUEUE_CONCURRENT);
    }
    return self;
}

-(void)addBlock:(dispatch_block_t)block
{
    _queuedBlock = [block copy];
    [self execute];
}

-(void)execute
{
    if(_currentlyRunningBlocks == 0 && _queuedBlock != nil)
    {
        _currentlyRunningBlocks = 1;

        __block dispatch_block_t c = [_queuedBlock copy];;
        __weak SingleDispatchQueue* weakSelf = self;
        _queuedBlock = nil;
        dispatch_async(_masterQueue, ^
        {
            c();
            dispatch_async(dispatch_get_main_queue(), ^()
            {
                [weakSelf next];
            });
        });
    }
}

-(void)next
{
    _currentlyRunningBlocks = 0;
    [self execute];
}

@end

So, this is a bit more code than the code in our previous post. However, it's pretty simple. 

Basically, we created a new class called: SingleDispatchQueue

This class has a single public method, called addBlock:(dispatch_block_t)block

We queue up the block that we had passed in, and we call our execute method. In the execute method, we check to make sure we're not currently in the middle of executing a block. If we are, we skip. 

Now, if a user calls addBlock again, while we're executing, you'll notice that we replace the existing queued block, with our new block, and thats how we satisfy the rules i laid out above. 

So, how is the code running asynchronously? Well that goes back to our contractor where we create our dispatch_queue_t called masterQueue. Note that we're creating a queue using DISPATCH_QUEUE_CONCURRENT

We then start out by calling dispatch_async, using our masterQueue and creating a new block. Within that block, we execute the block that was submitted via the addBlock method. Once that code has executed, we perform another dispatchAsync, but this time on the main thread. All that code does is execute a private method in our class, that tries and calls execute again. If there is nothing left to run, we exit the method and we're done. 

Stay tuned for Part 3 where we put everything we've learned so far together!

By Stephen Zaharuk (SteveZ)