Objective-C中的闭包性和匿名函数

http://www.xs-labs.com/en/archives/articles/objc-blocks/

1、关于
许多脚本语言都支持 lambda表达式匿名函数。这两个概念经常与 闭包性(closure)相关。例如在JavaScript、ActionScript或PHP(5.3之后)中都有相关的概念。其实在Objective-C语言中也提供了这两个概念的实现:叫做 block
 
自从Mac OS X 10.6之后,就可以使用block了,其实这样归功于Clang。
 
1.1 匿名函数
就如名字暗示的一样,匿名函数实际上就是一个没有名字或者标示( identifier)的函数。匿名函数只有内容(也可以叫做body),我们可以将其存储在一个变量中,以便之后使用,或者将其当做一个参数传递给另外一个函数使用。
 
在脚本语言的回调中经常使用到这个概念。
 
 
  1. function foo( callback ) 
  2.     callback(); 
 
这里有可能是定义了另外一个标准函数, 然后将这个标准函数当做参数传递给上面的函数:
 
 
  1. function bar() 
  2.     alert( 'hello, world' ); 
  3.   
  4. foo( bar ); 
 
不过这样一来, bar函数就会被声明在全局范围内,这就会带来一个风险:被另外一个相同名称的函数覆盖( override)了。
 
但是别担心,JavaScript语言允许callback函数在调用的时候才进行声明:
 
 
  1. foo 
  2.     function() 
  3.     { 
  4.         alert( 'hello, world' ); 
  5.     } 
  6. ); 
 
在上面,可以看到这个callback实际上并没有标示(identifier)。它也不会存在于全局范围,因此也不会与别的已有函数产生冲突。
 
我们也可以把callback存储到一个变量中,同样也不回存在于全局范围,不过我们可以通过这个变量对这个callback进行重复利用:
 
 
  1. myCallback = function() 
  2.     alert( 'hello, world' ); 
  3. }; 
  4.   
  5. foo( myCallback ); 
 

 1.2闭包性
闭包性这个概念是允许一个函数访问其所声明上下文中的变量,甚至是在不同的运行上下文中。
 
下面我们再来看看JavaScript的相关代码:
 
 
  1. function foo( callback ) 
  2.     alert( callback() ); 
  3.   
  4. function bar() 
  5.     var str = 'hello, world'
  6.   
  7.     foo 
  8.     ( 
  9.         function() 
  10.         { 
  11.             return str; 
  12.         } 
  13.     ); 
  14.   
  15. bar(); 
 
上面的代码中,callback被从bar函数的运行上下文中传递给了foo函数,该callback函数返回变量str的值。
 
不过在这里请注意,变量str是声明在bar函数中的,也就是说这个变量仅存于bar函数中。
 
而callback是在另外一个不同的函数中被执行的(跟变量str不在一起),我们这是可能会猜测foo函数中什么也不会显示出来。
 
但是,在这里引入了闭包性这个概念。
 
也就是说在不同的函数中(运行上下文中),一个函数可以访问到变量所声明上下文中的内容。
 
因此上面的代码中,callback可以访问到str变量——即使这个callback所在的foo函数不能直接访问这个str变量。
 
2、Objective-C中的实现
实际上闭包性和匿名函数在Objective-C中是可以使用的,只不过Objective-C是构建于C语言之上,属于强类型编译语言,所以跟上面介绍的解释性脚本语言有许多不同之处。
 
注意:block其实在纯C或C++(以及Objective-C++)中都是可用的。
 
在标准C函数中,定义block(匿名函数)之前需要先声明原型。
 
block的语法有一点点棘手,不过要是熟悉函数指针的话,就非常容易理解了。
 
下面是block的原型:
 
 
  1. NSString * ( ^ myBlock )( int ); 
上面的代码是声明了一个block(^)原型,名字就叫做myBlock,携带一个int参数,返回只为NSString类型的指针。
 
下面来看看block的定义:
 
 
  1. myBlock = ^( int number ) 
  2.     return [ NSString stringWithFormat: @"Passed number: %i", number ]; 
  3. }; 
 
如上所示,将一个函数体赋值给了myBlock变量,其接收一个名为number的参数。该函数返回一个NSString对象。
 
注意:不要忘记block后面的分号。
 
在脚本语言中是可以忽略掉分号的,但是在编译性语言(如Objective-C)是必须有的。
 
如果没有写这个分号,编译器会产生一个错误,当然也不会生成可执行文件。
 
定义好block之后,就可以像使用标准函数一样使用它了:
 
 
  1. myBlock(5); 
下面是完整的Objective-C程序源代码:
 
 
  1. #import <Cocoa/Cocoa.h> 
  2.   
  3. int main( void ) 
  4.     NSAutoreleasePool * pool; 
  5.     NSString * ( ^ myBlock )( int ); 
  6.   
  7.     pool    = [ [ NSAutoreleasePool alloc ] init ]; 
  8.     myBlock = ^( int number ) 
  9.     { 
  10.         return [ NSString stringWithFormat: @"Passed number: %i", number ]; 
  11.     }; 
  12.   
  13.     NSLog( @"%@", myBlock(5) ); 
  14.   
  15.     [ pool release ]; 
  16.   
  17.     return EXIT_SUCCESS; 
 
我们可以用下面的命令来编译(在Terminal中):
 
 
 
  1. gcc -Wall -framework Cocoa -o test test.m 
上面的命令会根据test.m源代码文件生成一个名为name的可执行文件。可以用下面的命令来运行这个可执行文件:
 
 
  1. ./test 
如果不把block赋值给变量的话,可以忽略掉block原型的声明,例如直接将block当做参数进行传递。如下所示:
 
 
  1. someFunction( ^ NSString * ( void ) { return @"hello, world" } ); 
注意,上面这种情况必须声明返回值的类型——这里是返回NSString对象。
 
2.1将BLOCK当做参数来传递
之前说过了,block可以当做参数传递给某个C函数。如下所示:
 
 
  1. void logBlock( NSString * ( ^ theBlock )( int ) ) 
  2.     NSLog( @"Block returned: %@", theBlock() ); 
 
由于Objective-C是强制类型语言,所以作为函数参数的block也必须要指定返回值的类型,以及相关参数类型(如果需要的话)。
 
其实在Objective-C方法中也是一样的:
 
 
  1. - ( void )logBlock: ( NSString * ( ^ )( int ) )theBlock; 
 
2.2闭包性
之前有说过,闭包性在Objective-C中是可用的,只不过其行为跟解释性语言有所不同罢了。我们来看看下面的程序:
 
 
  1. #import &lt;Cocoa/Cocoa.h&gt; 
  2.   
  3. void logBlock( int ( ^ theBlock )( void ) ) 
  4.     NSLog( @"Closure var X: %i", theBlock() ); 
  5.   
  6. int main( void ) 
  7.     NSAutoreleasePool * pool; 
  8.     int ( ^ myBlock )( void ); 
  9.     int x; 
  10.   
  11.     pool = [ [ NSAutoreleasePool alloc ] init ]; 
  12.     x    = 42; 
  13.   
  14.     myBlock = ^( void ) 
  15.     { 
  16.         return x; 
  17.     }; 
  18.   
  19.     logBlock( myBlock ); 
  20.   
  21.     [ pool release ]; 
  22.   
  23.     return EXIT_SUCCESS; 
 
上面的代码在main函数中声明了一个整型,并赋值42,另外还声明了一个block,该block会将42返回。
 
然后将block传递给logBlock函数,该函数会显示出返回的值42。
 
即使是在函数logBlock中执行block,而block又声明在main函数中,但是block仍然可以访问到x变量,并将这个值返回。
 
注意:block同样可以访问全局变量,即使是static。
 
下面来看看第一点不同之处:通过block进行闭包的变量是const的。也就是说不能在block中直接修改这些变量。
 
来看看当block试着增加x的值时,会发生什么:
 
 
  1. myBlock = ^( void ) 
  2.     x++ 
  3.   
  4.     return x; 
  5. }; 
 
编译器会生成一个错误:大概意思是在block中x变量是只读的。
 
不过也别担心,为了允许在block中修改变量,也是可以做到的:用__block关键字来声明变量即可。
 
基于之前的代码,给x变量添加__block关键字,如下:
 
 
  1. __block int x; 
 
2.3内存管理
从C语言的角度来看,实际上block是一个结构体,可以被拷贝和销毁的。有两个函数可以使用:Block_copy和Block_destroy()。
 
而在Objective-C中,block可以接收retain、release和copie消息,这就跟普通对象一样。如果一个block需要被存储下来供以后使用,这些消息是非常重要的(例如,将block存储到一个类的实例变量中)。例如,为了避免错误的使用block,对block进行retain是非常有必要的。
 
2.4示例
Block可以用在许多不同的环境中,这样可以让代码更加简单,以及减少函数声明的数量。
 
下面有一个实例:
我们将给NSArrary类添加一个static方法(类方法),该方法通过一个callback,根据另外一个数组中的内容产生一个新的数组。
 
在PHP程序员眼里,该方法就如一个array_filter()。
 
首先,需要为NSArray类声明一个category。(通过category可以给已有的类添加新方法)。
 
 
  1. @interface NSArray( BlockExample ) 
  2.   
  3. + ( NSArray * )arrayByFilteringArray: ( NSArray * )source withCallback: ( BOOL ( ^ )( id ) )callback; 
  4.   
  5. @end 
 
上面,声明了一个方法,该方法返回一个NSArray对象,另外接收两个参数:NSArray对象,以及一个callback (为block)。
 
在callback中会判断根据传入数组参数中的每一个元素。并将返回一个boolean值,以确定当前array中的元素是否需要存储到返回的数组中。
 
block只有一个参数,代表数组中的某个元素。
 
我们来看看该方法的具体实现:
 
 
  1. @implementation NSArray( BlockExample ) 
  2.   
  3. + ( NSArray * )arrayByFilteringArray: ( NSArray * )source withCallback: ( BOOL ( ^ )( id ) )callback 
  4.     NSMutableArray * result; 
  5.     id               element; 
  6.   
  7.     result = [ NSMutableArray arrayWithCapacity: [ source count ] ]; 
  8.   
  9.     for( element in source ) { 
  10.   
  11.         if( callback( element ) == YES ) { 
  12.   
  13.             [ result addObject: element ]; 
  14.         } 
  15.     } 
  16.   
  17.     return result; 
  18.   
  19. @end 
 
上面的代码中,首先是创建了一个可以动态改变尺寸的数组:NSMutableArray,然后根据source array的数目来初始化该数组。
 
然后对source array中的每个元素进行迭代, 如果callback返回值为YES的话,就将该元素添加到result数组中。
 
下面是使用该方法的一个完整示例:利用callback构建一个数组:该数组中只包含source array中为NSString类型的元素:
 
 
  1. #import &lt;Cocoa/Cocoa.h&gt; 
  2.   
  3. @interface NSArray( BlockExample ) 
  4.   
  5. + ( NSArray * )arrayByFilteringArray: ( NSArray * )source withCallback: ( BOOL ( ^ )( id ) )callback; 
  6.   
  7. @end 
  8.   
  9. @implementation NSArray( BlockExample ) 
  10.   
  11. + ( NSArray * )arrayByFilteringArray: ( NSArray * )source withCallback: ( BOOL ( ^ )( id ) )callback 
  12.     NSMutableArray * result; 
  13.     id               element; 
  14.   
  15.     result = [ NSMutableArray arrayWithCapacity: [ source count ] ]; 
  16.   
  17.     for( element in source ) { 
  18.   
  19.         if( callback( element ) == YES ) { 
  20.   
  21.             [ result addObject: element ]; 
  22.         } 
  23.     } 
  24.   
  25.     return result; 
  26.   
  27. @end 
  28.   
  29. int main( void ) 
  30.     NSAutoreleasePool * pool; 
  31.     NSArray           * array1; 
  32.     NSArray           * array2; 
  33.   
  34.     pool   = [ [ NSAutoreleasePool alloc ] init ]; 
  35.     array1 = [ NSArray arrayWithObjects: @"hello, world", [ NSDate date ], @"hello, universe", nil ]; 
  36.     array2 = [ NSArray 
  37.                     arrayByFilteringArray: array1 
  38.                     withCallback:          ^ BOOL ( id element ) 
  39.                     { 
  40.                         return [ element isKindOfClass: [ NSString class ] ]; 
  41.                     } 
  42.              ]; 
  43.   
  44.     NSLog( @"%@", array2 ); 
  45.   
  46.     [ pool release ]; 
  47.   
  48.     return EXIT_SUCCESS; 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值