浅谈Blocks
by Robin Lu on Sep.06, 2009, about blocks, code, mac, programming, xcode
在上次的Snow Leopard tech review摘要中提到,Snow Leopard(实际上是Xcode 3.2附带的编译器)开始支持Blocks。这是一个非常有意思的功能,以前通常只有一些动态语言支持,而现在,C/C++/Objective-C也可以用上这个功能了。
Block, 简单的说,就是一个函数对象,和其它类型的对象一样,你可以创建它,可以赋给一个变量,也可以作为函数的参数来传递。计算机科学中,更常用的名字是”closure”或者”lambda”。先通过一个例子看看什么是Block:
1 2 3 4 5 6 7 | 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”。
September 7th, 2009 on 1:52 am
请教这个和函数指针有啥本质的区别?
September 7th, 2009 on 6:56 am
楼上,和着我都白写了?
September 7th, 2009 on 9:25 am
估计sangshuduo 也就看个标题,然后就回复了
September 7th, 2009 on 10:02 am
哈哈,确实看了一半就跳过去了。抱歉骚扰了。。。
September 7th, 2009 on 1:42 pm
这个功能好啊,iphone上能用么?既然是Xcode的功能,应该不是只能用于SL吧?
September 7th, 2009 on 2:08 pm
iphone目前没有,也许快了吧。
不过有第三方实现,可以用在iphone上,比如http://code.google.com/p/plblocks/
September 13th, 2009 on 9:54 pm
还是希望在iPhone上能支持blocks,这样灵活性大点。不过如果macruby能支持iPhone,而且性能过得去的话,我估计我会改用macruby来写iPhone程序…
September 16th, 2009 on 3:40 pm
顶楼上的~
September 17th, 2009 on 10:48 pm
macruby 0.5rc两周内发布,正式版年底,不会加入iphone支持,这个可能和苹果的规划有关系,看0.6的规划也没加入这个需求。
不过难说是否会有第三方支持出现
November 11th, 2009 on 11:18 pm
呵呵,小僧去看看那个iPhone的第三方实现去 XD