石锅拌饭

浅谈Blocks

by Robin Lu on Sep.06, 2009, about , , , ,

在上次的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 Comments for this entry

Search

Archives

Browse by tags

agile apple blog book design ecto extension firefox git google hack ichm iphone keyword life mac madfox movie nonsense opensource plugin pm ruby rubyonrails sns software startup wordpress work 财帮子