http://www.robinlu.com/blog/archives/492
Block, 简单的说,就是一个函数对象,和其它类型的对象一样,你可以创建它,可以赋给一个变量,也可以作为函数的参数来传递。计算机科学中,更常用的名字是”closure”或者”lambda”。先通过一个例子看看什么是Block:
void (^hello)(char*);
hello = ^(char* str) {
NSLog(@"hello %s", str);
};
hello("robin");
这段代码申明了一个block变量hello,然后用一个block对象为它赋值,最后调用了这个函数。这是打印出的结果:
2009-09-06 16:36:12.693 blocks[6379:903] hello robin
再看一个例子。玩ruby的都知道each方法,可以很方便的遍历一些数据组合。比如:
[1,2,3].each {|x|
puts x
}
就可以打印出
1
2
3
现在,我们也可以为NSArray加上这样的方法。我为NSArray写了一个Category,加入下面函数的实现:
- (void)each:(void (^)(id))block
{
for (id obj in self) {
block(obj);
}
}
很简单,我们就有了一个属于NSArray的each。看一个调用的例子:
NSArray * array = [NSArray arrayWithObjects:@"tom", @"jerry", @"robin", nil];
[array each:^(id obj) {NSLog(@"hello %@", obj);}];
首先,生成了一个数组对象,里面有三个名字,然后分别向这三个名字说hello。
再来为NSArray实现一个select方法,这个方法为每一个数组中的对象调用block,这个block做出选择,选中的返回true,最后select函数返回一个数组,其中包含所有选中的对象:
- (NSArray *)select:(BOOL (^)(id))block
{
NSMutableArray *rslt = [NSMutableArray array];
for (id obj in self) {
if (block(obj)) {
[rslt addObject:obj];
}
}
return rslt;
}
再看一个调用的例子:
NSArray * array = [NSArray arrayWithObjects:@"tom", @"jerry", @"robin", nil];
[[array select:^(id obj) {
return [obj isEqualToString:@"robin"];
}] each:^(id obj) {
NSLog(@"hello %@ only", obj);
}];
依然生成包含三个名字的数组,但是先调用select,只选出’robin’,然后只向选出的名字说’hello’。
看到现在,应该已经对block有一个初步的认识了。也许会产生一个疑问,block和函数指针有什么区别?
第一个区别,函数指针是对一个函数地址的引用,这个函数在编译的时候就已经确定了。而block是一个函数对象,是在程序运行过程中产生的。在一个作用域中生成的block对象分配在栈(stack)上,和其他所有分配在栈上的对象一样,离开这个作用域,就不存在了。这是一个Xcode文档中的错误案例:
void dontDoThis() {
void (^blockArray[3])(void); // an array of 3 Block references
for (int i = 0; i < 3; ++i) {
blockArray[i] = ^{ printf("hello, %d\n", i); };
// WRONG: Block literal scope is "for loop"
}
}
另外,block对象在一个作用域中,在这个block之中可以访问同在这个作用域中的本地变量。我们来看一个例子:
void say(NSString *something)
{
NSArray * array = [NSArray arrayWithObjects:@"tom", @"jerry", @"robin", nil];
[array each:^(id obj) {
NSLog(@"%@, %@", something, obj);
}];
}
这个函数是向数组中的名字说点什么,具体说什么,在block中并不确定. 因为each本身接口的限制,也没法通过函数的参数传入。如果用函数指针来实现,就不可避免的要引入全局或者静态变量,而使用block,则完全没有这个问题。避免使用全局以及静态变量最直接的现实意义就是,更容易写出线程安全的程序。
通常情况下,block对同作用域中本地变量是以只读形式访问的,如果希望以读写方式来访问,需要在申明这个变量时加上__block。
本文的例子放在github上了,其中对NSArray还实现了map和reduce。
如果希望对Blocks做更多的了解,可以参看Xcode中的"Blocks Programming Topics"。