In the first part of this series, we learned how to declare and call basic Objective-C blocks. The motivation was to understand how to effectively use the new APIs in iOS 4 that take blocks as parameters. In this installment we're going to shift our focus toward writing our own methods that take blocks. By understanding how to use blocks in your own code, you'll have another design technique in your repertoire. And you might just find that blocks make your code easier to read and maintain.

Writing Methods That Take Blocks

We left off last time with a challenge: Write a Worker class method that takes a block and repeatedly calls it a given number of times, passing in the iteration count each time. If we wanted to triple the numbers 1 through 5, for example, here's how we would call the method with an inline block:

[Worker repeat:5 withBlock:^(int number) {
return number * 3;
}];

I often design classes this way. I start by writing the code that calls a fictitious method I need, simply as a way of shaping the API before committing to it. Once the method call feels right, I go ahead and implement the method. In this case, the method name repeat:withBlock:just doesn't feel right to me. (I know, that's the name I used in the previous article, but I've changed my mind.) The name is confusing because the method doesn't actually repeat the same thing. It iterates from 1 to the given number, and passes the block the iteration count.

So let's get started on the right foot by renaming it:

[Worker iterateFromOneTo:5 withBlock:^(int number) {
return number * 3;
}];

There, I'm pretty happy with a class method named iterateFromOneTo:withBlock: that takes two parameters: an int representing the number of times to call the supplied block and the block itself. Now let's implement the method.

For starters, how do we declare the iterateFromOneTo:withBlock: method? Well, we need to know the types of both parameters. The first parameter is easy; it's an int. The second parameter is a block, and all blocks have an associated type. In this example, the method will take any block that accepts a single int parameter (the iteration count) and returns an int value as the result. Here's the actual block type:

int (^)(int)

Given the name of the method and its parameter types, we're ready to declare the method. This being a Worker class method, we declare it in the Worker.h file:

@interface Worker : NSObject {
} + (void)iterateFromOneTo:(int)limit withBlock:(int (^)(int))block; @end

Now, block parameters can be difficult to parse at first sight. The trick is to remember that all method parameters in Objective-C have two components: the parameter type in parentheses followed by the name of the parameter. In this example, the limit parameter is an int type and the block parameter is an int (^)(int) block type. (You can name the parameter whatever you like; block isn't a special name.)

The implementation of the method over in the Worker.m file is pretty straightforward:

#import "Worker.h"

@implementation Worker

+ (void)iterateFromOneTo:(int)limit withBlock:(int (^)(int))block {
for (int i = 1; i <= limit; i++) {
int result = block(i);
NSLog(@"iteration %d => %d", i, result);
}
} @end

It spins through a loop, calling the block with the iteration count each time through the loop and printing the block's result. Remember that once we have a block variable in scope, calling the block is just like calling a function. In this case, the block parameter references a block. So when block(i) is executed on the highlighted line, it invokes the code in the block. When the block returns, control picks back up on the next line.

We can now call the iterateFromOneTo:withBlock: method with an inline block, like so:

[Worker iterateFromOneTo:5 withBlock:^(int number) {
return number * 3;
}];

Alternatively, instead of using an inline block, we could pass the method a predefined block variable of the appropriate type:

int (^tripler)(int) = ^(int number) {
return number * 3;
}; [Worker iterateFromOneTo:5 withBlock:tripler];

In either case, the numbers 1 through 5 are tripled and we get this output:

iteration 1 => 3
iteration 2 => 6
iteration 3 => 9
iteration 4 => 12
iteration 5 => 15

Of course we can pass blocks that perform any arbitrary computation. Want to square all the numbers? No problem, just give the method a different block:

[Worker iterateFromOneTo:5 withBlock:^(int number) {
return number * number;
}];

Now that our code works, let's tidy it up a bit.

Typedef Is Your Friend

Declaring block types can get messy in a hurry. Even in our simple example the function pointer syntax leaves a lot to be desired:

+ (void)iterateFromOneTo:(int)limit withBlock:(int (^)(int))block;

Imagine the block taking multiple parameters, some of which are pointer types, and you're quickly in for a world of hurt. To improve readability and eliminate duplication in the .h and .m files, we can revise our Worker.h file to use a typedef, like so:

typedef int (^ComputationBlock)(int);

@interface Worker : NSObject {
} + (void)iterateFromOneTo:(int)limit withBlock:(ComputationBlock)block; @end

typedef is a C keyword that assigns a synonym to a type. Think of it as the nickname for a type that has a cumbersome real name. In this case, we've defined ComputationBlock to refer to a block type that takes an int and returns an int. Then, when defining theiterateFromOneTo:withBlock: method, we simply use ComputationBlock as the block parameter type.

Likewise, in the Worker.m file, we can simplify the code by using ComputationBlock:

#import "Worker.h"

@implementation Worker

+ (void)iterateFromOneTo:(int)limit withBlock:(ComputationBlock)block {
for (int i = 1; i <= limit; i++) {
int result = block(i);
NSLog(@"iteration %d => %d", i, result);
}
} @end

Ah, much better! The code is easier to read and doesn't duplicate the block type syntax across files. In fact, you can use ComputationBlock in place of the actual block type anywhere in your application that imports Worker.h.

You'll run into similar typedefs in the new iOS 4 APIs. For example, the ALAssetsLibrary class defines the following method:

- (void)assetForURL:(NSURL *)assetURL
resultBlock:(ALAssetsLibraryAssetForURLResultBlock)resultBlock
failureBlock:(ALAssetsLibraryAccessFailureBlock)failureBlock

This method takes two blocks: one to invoke if the asset was found and one to invoke if the asset was not found. They're typedef'd as follows:

typedef void (^ALAssetsLibraryAssetForURLResultBlock)(ALAsset *asset);
typedef void (^ALAssetsLibraryAccessFailureBlock)(NSError *error);

You can then use ALAssetsLibraryAssetForURLResultBlock and ALAssetsLibraryAccessFailureBlock within your application to refer to the respective block types.

I recommend always using a typedef when writing a public method that takes a block. It helps keep your code tidy and expresses the intent of the block to developers who may use your code.

Another Look At Closures

You may recall that blocks are closures. We briefly touched on closures in the first part of this series, but the example wasn't particularly interesting. And I hinted that closures would become more useful when we started passing blocks around to methods. Well, now that we know how to write methods that take blocks, let's try another closure example:

int multiplier = 3;

[Worker iterateFromOneTo:5 withBlock:^(int number) {
return number * multiplier;
}];

We're using the same iterateFromOneTo:withBlock: method we wrote earlier, but in this example the block has a subtle, yet very important, difference. Rather than hard-coding the multiplier inside the block as in previous examples, this block uses a local multiplier variable declared outside of the block. The result of running the method is the same as before; it triples the numbers 1 through 5:

iteration 1 => 3
iteration 2 => 6
iteration 3 => 9
iteration 4 => 12
iteration 5 => 15

That this code runs at all is an example of the power of closures. The code defies typical scoping rules. In particular, consider that the multiplier local variable is out of scope when the block is called from inside the iterateFromOneTo:withBlock: method.

Remember, however, that blocks also capture their surrounding state. When a block is declared it automatically takes a (read-only) snapshot of all the variables in scope that the block uses. Because our block uses the multiplier variable, the variable's value is captured in the block to be used later. That is, the multiplier variable has become a part of the state of the block. And when the block is passed to theiterateFromOneTo:withBlock: method, the block's state goes along for the ride.

OK, so what if we wanted to modify the multiplier variable inside the block? Say, for example, each time the block was called we wanted the multiplier to become the result of the last computation. You might be tempted to just assign to multiplier inside the block, like this:

int multiplier = 3;

[Worker iterateFromOneTo:5 withBlock:^(int number) {
multiplier = number * multiplier;
return multiplier; // compile error!
}];

But the compiler won't let you get away with that. You'll get the error "Assignment of read-only variable 'multiplier'". This happens because a block effectively gets a const copy of stack (local) variables that it uses. These variables are immutable inside the block.

If you want to be able to modify an externally-declared variable inside a block, you need to prefix the variable with the new __block storage type modifier, like so:

__block int multiplier = 3;

[Worker iterateFromOneTo:5 withBlock:^(int number) {
multiplier = number * multiplier;
return multiplier;
}]; NSLog(@"multiplier => %d", multiplier);

This code compiles and runs as follows:

iteration 1 => 3
iteration 2 => 6
iteration 3 => 18
iteration 4 => 72
iteration 5 => 360
multiplier => 360

It's important to note that after the block runs, the value of the multiplier variable has been changed to 360. In other words, the block doesn't modify a copy of the variable. Any variable declared using the block modifier is passed by reference into the block. In fact, blockvariables are shared with all other blocks that may use that variable. A word of caution is in order here: block is not to be used casually. There is a marginal cost involved in moving things to the heap and, unless you really need to modify a variable, you shouldn't just make it a block variable.

Writing Methods That Return Blocks

Every once in a while it's handy to write a method that creates and returns a block. Let's look at a contrived (and dangerous!) example:

+ (ComputationBlock)raisedToPower:(int)y {
ComputationBlock block = ^(int x) {
return (int)pow(x, y);
};
return block; // Don't do this!
}

This method simply creates a block that computes x raised to the power y, then returns it. It uses our old typedef'd friend ComputationBlock.

Here's how we expect to use it:

ComputationBlock block = [Worker raisedToPower:2];
block(3); // 9
block(4); // 16
block(5); // 25

The block we get back remembers that we want everything raised to the power of the exponent (2 in this case). When we call the block with a number, it should return the result of raising the number to the power of the exponent. As it stands, though, if we were to run this code it would blow up with an EXC_BAD_ACCESS runtime error.

What gives? Well, the key to fixing the problem lies in understanding how blocks are allocated. A block starts its life on the stack because allocating memory on the stack is relatively fast. Stack variables, however, are destroyed when they're popped off the stack. This happens when returning from a method.

Looking back out our raisedToPower: method, we see that we're creating a block (on the stack) and returning it. That in turn causes the scope within which the block was declared to be destroyed, which includes the block. So when we go to use the returned block variable, it's a time bomb.

The fix is to move the block from the stack to the heap before returning it. That sounds complicated, but it's actually very easy. We simply call copy on the block and the block will be automatically moved to the heap. Here's the revised method that works as expected:

+ (ComputationBlock)raisedToPower:(int)y {
ComputationBlock block = ^(int x) {
return (int)pow(x, y);
};
return [[block copy] autorelease];
}

Notice that since we called copy, we must balance it with an autorelease in this case to be good stewards of memory. Otherwise the calling code would need to remember to release the block, which goes against the ownership conventions.

It's not often that you need to copy a block, but returning a block allocated inside a method is one case where it's critical to make a copy to move the block to the heap. Be careful out there!

Putting It All Together

OK, let's put some of what we learned together into a slightly more practical example. Suppose we're designing a simple class that will play movies. Users of this class want to receive a callback when a movie is done playing so they can perform application-specific logic. It turns out that blocks are a convenient way to handle callbacks.

Let's start by writing the code from the perspective of a developer using this class:

MoviePlayer *player =
[[MoviePlayer alloc] initWithCallback:^(NSString *title) {
NSLog(@"Hope you enjoyed %@", title);
}]; [player playMovie:@"Inception"];

So we need a MoviePlayer class with two methods: initWithCallback: and playMovie:. The inititializer needs to take a block, stash it away, and then call the block at the end of the playMovie: method. The block takes one parameter (the movie title) and returns nothing. We'lltypedef the callback block type and use a property to hang onto the callback block. Remember, blocks are objects and you can use them as instance variables and/or properties. We'll use a property in this case to demonstrate the point.

Here's MoviePlayer.h:

typedef void (^MoviePlayerCallbackBlock)(NSString *);

@interface MoviePlayer : NSObject {
} @property (nonatomic, copy) MoviePlayerCallbackBlock callbackBlock; - (id)initWithCallback:(MoviePlayerCallbackBlock)block;
- (void)playMovie:(NSString *)title; @end

And here's the corresponding MoviePlayer.m:

#import "MoviePlayer.h"

@implementation MoviePlayer

@synthesize callbackBlock;

- (id)initWithCallback:(MoviePlayerCallbackBlock)block {
if (self = [super init]) {
self.callbackBlock = block;
}
return self;
} - (void)playMovie:(NSString *)title {
// play the movie
self.callbackBlock(title);
} - (void)dealloc {
[callbackBlock release];
[super dealloc];
} @end

In initWithCallback:, we assign the supplied block to the callbackBlock property. Because the property was declared to use copy semantics, the block is automatically copied, which moves it onto the heap. Then when the playMovie: method is invoked, we call the block passing in the movie title.

Now let's say a developer wants to tie our MoviePlayer class into an application that manages a queue of movies you plan to watch. Once you've watched a particular movie, it should be removed from your queue. Here's a trivial implementation that also demonstrates a closure:

NSMutableArray *movieQueue =
[NSMutableArray arrayWithObjects:@"Inception",
@"The Book of Eli",
@"Iron Man 2",
nil]; MoviePlayer *player =
[[MoviePlayer alloc] initWithCallback:^(NSString *title) {
[movieQueue removeObject:title];
}]; for (NSString *title in [NSArray arrayWithArray:movieQueue]) {
[player playMovie:title];
};

Notice that the block uses the local movieQueue variable, which becomes part of the state of the block. When the block is called it removes the movie title from the movieQueue array even though it's out of scope by that time. After all the movies have been played, the movieQueue will be empty.

A couple important things worth noting here:

  • The movieQueue variable is an array pointer, and we're not modifying where it points. We're modifying its contents. Therefore, we don't need to use the __block modifier.
  • To iterate through the movie queue we needed to create a copy of the movieQueue variable. Otherwise, if we used movieQueue directly, we'd be removing elements while trying it iterate through it. This causes an exception because enumeration in Objective-C is designed to be safe.
  • Instead of using a block, we could have declared a protocol, written a delegate class, and registered the delegate as a callback. Using an inline block in this example is simply more compact and convenient.
  • New functionality can be added without changing the MoviePlayer class. Another developer might pass in a block that tweets the movie title to your Twitter account, marks the title as being played in a Core Data store, or prompts you to rate the movie.

Next Steps

That's a wrap for this series. Thanks for tuning in! There's more to blocks, but what we've learned so far represents the majority of uses. If you want to dig even deeper, I suggest working through the following resources:

As a final takeaway, I hope you've seen how blocks offer a different style of programming that can inform the design of your application. When and where you use them is a judgement call, like any other design decision. Give blocks a try, and I look forward to your comments.

Have fun!

(Thanks to Matt Drance (@drance) and Daniel Steinberg (@dimsumthinking) for reviewing drafts of this article.)

iOS Developer Training

Want to learn how to build iOS apps from scratch? Consider attending an upcoming public iPhone/iPad Programming Studio, or scheduling a private course, taught by two experienced iOS developers. You'll come away ready to create your first iPhone/iPad app, or improve your existing app. It's a lot of fun!

Using Blocks in iOS 4: Designing with Blocks的更多相关文章

  1. iOS 中Block以及Blocks的使用,闭包方法调用

    OC: -(void)dataWithUrl:(NSString*)string AndId:(NSInteger)id returnName:(void(^)(NSString*name))back ...

  2. Using Blocks in iOS 4: The Basics

    iOS 4 introduces one new feature that will fundamentally change the way you program in general: bloc ...

  3. iOS 开发该选择Blocks还是Delegates

    http://www.cocoachina.com/ios/20150925/13525.html 前文:网络上找了很多关于delegation和block的使用场景,发现没有很满意的解释,后来无意中 ...

  4. iOS - Blocks

    iOS中Blocks的介绍     1. 什么是Blocks Blocks是C语言的扩充功能.就是:带有自动变量的匿名函数. 类似C语言的函数指针.但Blocks不是一个指针,而是一个不带名字的函数, ...

  5. 【转】教你爱上Blocks(闭包)

    Block 与传统代码相比较更加轻量,调用简洁方便,而且可以返回多个参数,使用Block可以让代码更加具有易读性,而我们在写回调时,也可以直接写在函数内部,而不用再去写一个回调函数 Block 闭包 ...

  6. Blocks and Variables

    Blocks and Variables https://developer.apple.com/library/ios/documentation/cocoa/conceptual/Blocks/A ...

  7. Blocks(闭包)

    转自:http://my.oschina.net/joanfen/blog/317644?fromerr=ATWzC3Y2 Block 与传统代码相比较更加轻量,调用简洁方便,而且可以返回多个参数,使 ...

  8. Blocks Programming Topics

    最近的工作中比较频繁的用到了Block,不在是以前当做函数指针的替代或者某些API只有Blocks形式的接口才不得已用之了,发现自己对其了解还是太浅,特别是变量的生存期,按惯例还是翻译官方文档,原文链 ...

  9. [翻译] Blocks and Variables

    Blocks and Variables https://developer.apple.com/library/ios/documentation/cocoa/conceptual/Blocks/A ...

随机推荐

  1. js基础之javascript函数定义及种类-普通涵数-自执行函数-匿名函数

    普通函数 1.不带参数 function fucname(){ alert("hello"); } funcname() 2.带参数 function funcname(arg){ ...

  2. Apache Compress-使用

    Apache Compress 是什么? Apache  提供的文件压缩工具. 运行环境 jdk 1.7 commons-compress 1.15 测试代码 package com.m.basic; ...

  3. luogu2023 [AHOI2009]维护序列

    线段树加乘懒标记裸题. #include <iostream> #include <cstdio> using namespace std; typedef long long ...

  4. 怎么使用瓦特平台下面的“代码工厂”快速生成BS程序代码

    这里说一下怎么使用瓦特平台下面的“代码工厂”快速生成程序代码 使用平台:windows+"visual studio 2010"+"SqlServer2000+" ...

  5. 用html5文件api实现移动端图片上传&预览效果

    想要用h5在移动端实现图片上传&预览效果,首先要了解html5的文件api相关知识(所有api只列举本功能所需): 1.Blob对象  Blob表示原始二进制数据,Html5的file对象就继 ...

  6. 基于web自动化测试框架的设计与开发(本科论文word)

  7. CSU-2172 买一送一

    CSU-2172 买一送一 Description ICPCCamp 有 n 个商店,用 1, 2, -, n 编号.对于任意 i > 1,有从商店 \(p_i\) 到 i 的单向道路. 同时, ...

  8. springcloud 高可用分布式配置中心

    SpringCloud教程七:高可用的分布式配置中心(SpringCloud Config) 当服务有很多 都要从服务中心获取配置时 这是可以将服务中心分布式处理 是系统具备在集群下的大数据处理 主要 ...

  9. 实战小项目之嵌入式linux图像采集与传输

    项目简介      本次编程实战主要是围绕嵌入式linux v4l2采集框架展开,包括以下几个部分: v4l2视频采集 IPU转码 framebuffer显示 自定义UDP简单协议进行传输 上位机软件 ...

  10. [oldboy-django][4python面试]有关csrf跨站伪造请求攻击

    1 csrf定义 - csrf定义:Cross Site Request Forgery,跨站请求伪造 举例来说: 网站A伪造了一个图片链接: <a href="http://www. ...