Using GCD and Blocks Effectively

With iOS 4.0 Apple introduced two new technologies to the iOS platform: Grand Central Dispatch, and blocks.  Simply put, it is to multi-threaded programming what fire is to a barbecue.  Sure you can do without it, but the end result is much better.

Despite all this, developers still seem to avoid using it. Some of the reasons for this, off the top of my head, could be backwards-compatibility for older versions of iOS, unfamiliarity with the funky syntax it uses, or simply a lack of practice.  The biggest thing I find however is a general misunderstanding about the importance of multi-threading among new developers, which was made worse by the difficulty of dealing with threads before blocks and GCD was released.

Fortunately there’s no reason to avoid multi-threaded programming in iOS, but before I dive into the specifics I’d like to point out just how important it is to use an asynchronous approach to development on iOS, or any mobile platform in general.

Why should I care about GCD?

Before going into the specifics of how to use blocks and Grand Central Dispatch, I’d like to explain why it’s important. If you don’t take anything else away from this article, these points are important.  Here it goes:

Don’t make the device wait around for you to figure out what you want it to do.

The CPUs in mobile devices are really weak, memory is limited, you have some really hard limitations that you just can’t work around.  Performance problems aren’t often caused by a single slow operation, but many inefficiencies in how you utilize the hardware.  Performing expensive calculations, querying a database, reading images from disk, or other activities all take time.  Are you using caches effectively? Are you performing expensive tasks in the main thread?  There are many other things that you may be doing that can block the CPU from doing what really needs to happen: Rendering your interface to the screen 60 times per second.  If you can’t meet that target, your app is failing to provide smooth animations, and it will stick out like a sore thumb compared to the other applications on the device.

Each thread on your device runs its own event loop.  The operating system picks up a page of code, runs it, and when that chunk of code exits out the next iteration of the loop picks up.  When rendering your display, the main thread wakes up, runs all the code necessary to assemble your UI, paints a picture onto a memory buffer, and throws that on the screen. It tries to do this 60 times per second.  If each iteration of this runloop takes more than 16ms to run, then your device will drop frames and your display will get jittery.  If it takes less than 16ms, this means there’s spare CPU cycles available to do other work.

The basics of asynchronous programming

Developers in general are quite lazy; in fact, laziness is a great virtue to have as a programmer.  It means we’d rather be doing something else, so we want to do as little work as possible so we can get on with other work.  So it’s quite understanding why so many applications are developed with lots of synchronous code: It’s just easier to think about problems in a linear fashion.  You break a problem down into a series of steps, and write the code necessary to do those steps in order. Fortunately for us most computers are fast enough that this technique works well.  But once you switch to a mobile platform, long linear functions need to be broken down into smaller steps.

Consider an example where you download an image over the network, you resize and scale it to a thumbnail, and then put it onto the screen.  Because any operation interacting with the UI must be performed on the main thread, the easy route is to do all the above as a single synchronous set of instructions on the main thread.  Doing so however will result in the UI freezing up during the download, image conversion, and image resizing operations.

// Download the image
NSURL *url = [NSURL URLWithString:@"http://bit.ly/nUX01h"];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
NSURLResponse *res = [[NSURLResponse alloc] init];
NSError *err = nil;
NSData *data = nil;
data = [NSURLConnection sendSynchronousRequest:req
                             returningResponse:&res
                                         error:&err];

// Convert the data to a UIImage
UIImage *image = [UIImage imageWithData:data];
      
// Scale the image
UIImage *thumbImage = nil;
CGSize newSize = CGSizeMake(90, (90 / image.size.width) * image.size.height);
UIGraphicsBeginImageContext(newSize);
[image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
thumbImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
 
// Add this to a UIImageView
self.imageView.image = thumbImage;


Not only is converting the image from data to a UIImage or scaling the image to a  thumbnail expensive, but depending on the speed of the user’s network (e.g. wifi, 3G, AT&T, etc) the download could take significantly longer.  So the best way is to break this up into multiple smaller steps. When each step finishes, the system will work on the next chunk.

Taking the above example of what not to do, lets refractor it slightly to use blocks.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
    NSURL *url = [NSURL URLWithString:@"http://bit.ly/nUX01h"];
    NSURLRequest *req = [NSURLRequest requestWithURL:url];
    [NSURLConnection sendAsynchronousRequest:req
                                       queue:[NSOperationQueue currentQueue]
                           completionHandler:
     ^(NSURLResponse *res, NSData *data, NSError *err) {
         // Convert the data to a UIImage
         UIImage *image = [UIImage imageWithData:data];
          
         // Scale the image
         UIImage *thumbImage = nil;
         CGSize newSize = CGSizeMake(90, (90 / image.size.width) * image.size.height);
         UIGraphicsBeginImageContext(newSize);
         [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
         thumbImage = UIGraphicsGetImageFromCurrentImageContext();
         UIGraphicsEndImageContext();
          
         dispatch_async(dispatch_get_main_queue(), ^{
             self.imageView.image = thumbImage;
         });
     }];
});

As you can see most of the code is the same, except we’re breaking things down into smaller discrete chunks.  Each block is nested, allowing code to be called only when it is needed.  I’ll go into more detail with this shortly, but see if you can piece together what’s different between these two samples of code.

Doing the above fully asynchronously without blocks is entirely possible, but results in more complicated code that is more difficult to maintain.  The biggest difficulty is in keeping track of values and properties between threads, and results in a lot more boilerplate code.  Using blocks you can simply access the variables accessible within your current scope, making everything much easier.

What is a block?

Blocks in essence are nested functions that can be passed around as a pointer that has access to variables defined in the scope the block is defined in.  If you have ever worked in a language that supports “closures” (e.g. JavaScript, Perl, etc) you’ll probably already be familiar with this.

Blocks in Objective-C are defined rather strangely, but once you get used to it they make sense.  The carat “^” character is used to define a block. For example, “^{ … }” is a block. More specifically, it is a block that returns “void” and accepts no arguments.  It is equivalent to a method such like: “- (void)something;” but there is no inherent name associated with the code block.

Defining a block that can accept arguments work very similarly.  If you wanted to supply an argument to a block, you define the block like so: ^(BOOL someArg, NSString* someStr) { … }.  When you use API calls that support blocks, you’ll be writing blocks that look similar to this, especially for animation blocks or NSURLConnection blocks as shown in the above example.

The easiest way to get started with blocks is to simply use existing APIs that support them. NSURLConnection above supports blocks, as do UIView animations, like so:

[UIView animateWithDuration:1.0
                 animations:^{
                     someView.alpha = 0;
                     otherView.alpha = 1;
                 }
                 completion:^(BOOL finished) {
                     [someView removeFromSuperview];
                 }];


If you have any experience with animating views without blocks, getting a completion handler to run after an animation has completed is extremely cumbersome, so much so that many developers don’t bother with them. Using the block-enabled alternative makes for much cleaner code, and makes it fun to create complex animations.

How does GCD tie into this?

Grand Central Dispatch is a facility for managing threads and queueing tasks in parallel efficiently, without you needing to worry about the boilerplate specifics around performance and startup / shutdown of threads.  Blocks and GCD are both completely different technologies, but since they work so well together it’s trivial to get started with it.

In order to use GCD, you use one of the functions that begin with “dispatch_“.  One of the common tasks a developer would want to perform is to run some small task in a background thread, and then run some other code on the main thread once that task has completed.  You may notice the call to “dispatch_async” up above, which explicitly runs a block on the main thread.

Dispatching blocks can either be triggered synchronously or asynchronously by using thedispatch_sync or dispatch_async functions, and they both take two arguments: The queue you want to dispatch your block on, and the block you’d like to dispatch.  Individual queues are analogous to threads, but the specifics about which thread your code will be run on is hidden from you, meaning the operating system can optimize the use of threads based on the number of cores your system possesses.

The two most common ways you’d get a pointer to a queue is to either fetch the main queue using “dispatch_get_main_queue()” or to fetch a queue with a specific priority, you usedispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) and specify the priority you’d like (DEFAULT, LOW, HIGH, or BACKGROUND).

How do I create my own block APIs?

The most difficult part of dealing with blocks for many people is how to define your own methods or APIs that utilize blocks, mostly due to the confusing syntax used to define them.  Once you figure out how the syntax is broken down however, it’s quite easy.  Let me start with a few examples.

So first, let me show you how to define a block that takes no arguments and returns nothing.

- (void)noArgBlock:(void (^)(void))block;

The syntax we’re concerned about here is in the definition of the “block” argument.  As you know, you declare the type of an argument in parenthesis immediately before the name of the attribute.  So it’s easiest to ignore those outer parenthesis when dealing with block definitions.

void (^)(void)

Now things are looking a little more straight-forward, and a little closer to a standard C function.  The first word is the return type we expect a block with this definition to return.  Since we said we were creating a basic block that returned nothing, this is set to “ void “.

Normally in a C function definition the very next thing after the return type would be the function name.  But since blocks aren’t actually named, instead we use “(^)” to denote that it’s an unnamed block that we’re defining.  So far so good, right?

Finally we define the parameters this block will accept.  Again with regular C functions this should be familiar, since you give a parenthesized list of arguments you’ll be providing to this function.  Since we’re not accepting any arguments to this block, we indicate that by putting in “void“.

And that’s it! Lets show you how you’d use that in your function!

- (void)noArgBlock:(void (^)(void))block {
    // Do something...
     
    // Call our block
    block();
}


When you want to invoke a block function supplied to your method, you can just call it like you would any C function.  But what if we want to call that block on a different queue or thread?  That’s easy enough too.

- (void)noArgBlock:(void (^)(void))block {
    // Do something...
     
    // Call our block
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), block);
}


We’re using the GCD dispatch function here to trigger our block asynchronously on a low-priority queue.  Blocks can be passed around as variables, so we don’t have to explicitly define our own inline block here like we did in the animation or image examples at the beginning of this article.

Armed with all that syntax in mind, lets create a block that accepts an argument, and I’ll show you how to use it.

- (void)hasArgsBlock:(void (^)(NSString *name, BOOL animated))block {
    block(@"FOO", NO);
}


Remember from what I said that the second set of parenthesis in a block definition is the argument list you’ll be supplying to the block.  In this case, we’re accepting a callback that takes an NSString and a BOOLargument.  This is a contrived example since we’re simply dispatching the block immediately, but we could just as easily include our block call in our own block callback as the result of a UIView animation, anNSURLConnection completion block, or anything else.  A user of this method could look like this:

[self hasArgsBlock:^(NSString *name, BOOL animated) {
    NSLog(@"Name: %@, animated %d", name, animated);
}];


How do I store a block as an ivar?

This is a much trickier question, and is one of the things that makes people run screaming from blocks, assuming they didn’t already do so when they saw the “void(^)(void)” syntax soup.  Consider the example where you want to store a block for later use, or re-use.  If we want to store a void block, a simple solution is to use a built-in GCD type to reference blocks.

The dispatch_block_t type can be used.

@interface MyViewController : UIViewController {
    dispatch_block_t _simpleBlock;
}
 
@property (nonatomic, copy) dispatch_block_t simpleBlock;


If you want to store a block that either accepts arguments or returns a value, it’s possible to use the block-parenthesis-soup as the type definition for an ivar.  For example, assume we want an ivar named “completionBlock” defined within our class. We can do so with the following code:

@interface MyViewController : UIViewController {
    void (^completionBlock)(NSString *name, BOOL animated);
}

However that looks hideous, and is difficult to maintain. Instead it’s in your best interest to define your own custom type based on the block-soup you’d use in a method declaration. If we wanted to create a typedef for our above block, the one that accepts a name and animated property.  We could do so with the following declaration.

typedef void (^MyCompletionBlock)(NSString *name, BOOL animated);

By including a name after the carat symbol you can assign the block definition a name.  Then, later, you can use that typedef in an ivar, in a property, or even as an argument to another method.

@interface MyViewController : UIViewController {
    MyCompletionBlock _completeBlock;
}
 
@property (nonatomic, copy) MyCompletionBlock completeBlock;
 
- (void)storeMyBlock:(MyCompletionBlock)block;

Since blocks are objects, you need to copy them if you want to store them as a property, and don’t forget to release them when you’re done with them, or in your class’ dealloc method.

Closures, variables, and retain cycles

As I mentioned above, blocks work as closures, meaning you have access to the local variables within the scope a block is defined in.  This makes your code really easy to work with, but there are some limitations that are imposed for some very good reasons.  Consider the following example:

NSArray *someArray = ...;
int myCount = 0;
[someArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    int someValue = [obj intValue];
    myCount += someValue;
}];

This is a great way to perform an operation against an array or set using blocks because it will take advantage of multiple cores to be able to run your code faster when it’s possible.  However the above code won’t work if you actually tried it, because there’s a specific limitation in blocks which prevent you from writing to values declared outside of the block.  Think of it as a thread-locking problem. If you were to build that above code, you’d get an error that says “Variable is not assignable (missing __block type specifier)”.

It’s simple enough to work around this.  You can use the “__block” flag when declaring a variable so you can allow it to be writeable from within a block.  For example, the above code could just have this one change and everything would work as you’d expect:

__block int myCount = 0;

One other gotcha is regarding retain cycles when using the special “self” variable.  In the case where you retain a block and, within that block you refer to your “self” object, this will cause an unbroken retain cycle where the block is retained by self, and “self” is retained by the block.

self.completionBlock = ^(NSData *result) {
    [self saveFile:result];
};

This little block will take some data returned from some code and save it in some way.  However because the “self” variable is retained by the block, as well as the scope outside of the block, this block will leak memory.  Yet again, this is easily solved by declaring a “weak” reference to the self variable, and then using that within the block.  If you’ve ever worked in JavaScript you’ll be familiar with this pattern.  The resulting code would look like the following:

__block __typeof__(self) _self = self;
self.completionBlock = ^(NSData *result) {
    [_self saveFile:result];
};

Bringing it all together

Blocks are an extraordinary enhancement to the language, and if you take full advantage of it in your applications you’ll be able to seamlessly take advantage of faster language features, your code can spread across multiple cores seamlessly, and you can easily break serialized tasks into fast divisible units of labour.

Take a look through the various block APIs that Apple supports, and embrace it as much as you can.  In the long run it’ll make your code easier to understand, and faster as technology advances.  Don’t be left behind!


From: http://nachbaur.com/blog/using-gcd-and-blocks-effectively


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值