浅谈Blocks

在上次的Snow Leopard tech review摘要中提到,Snow Leopard(实际上是Xcode 3.2附带的编译器)开始支持Blocks。这是一个非常有意思的功能,以前通常只有一些动态语言支持,而现在,C/C++/Objective-C也可以用上这个功能了。

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"。

10 thoughts on “浅谈Blocks

Comments are closed.