1.定义
闭包是一个允许访问自由变量(局部变量)的匿名函数。自由变量:跟block声明在同一个作用域内的局部变量。
2.语法结构
返回类型 (^块名称)(参数列表)
int (^myblock)(int a, int b) ; //声明一个块类型和块名,myblock为块名,块类型为int (^)(int a, int b);
myblock= ^(int a, int b) { return a+b;}; //定义块
int c = myblock(1,2); //c = 3; //调用块
从上面代码,块跟一个变量一样,先声明,定义,访问。
块变量与一般的C语言变量完全相同,可以作为以下用途。
局部变量
函数参数
静态变量
静态全局变量
全局变量
例1.块作为属性变量
@interface
EOCClass :
NSObject
@property ( nonatomic , strong ) int (^variableName)( int a, int b
);
@property ( nonatomic , strong ) int (^variableName)( int a, int b
);
@end
@implementation
EOCClass
- ( id )init {
- ( id )init {
if((self= [superinit])) {
_variableName= ^(inta,intb) { //定义一个块,但块里面的代码还没被调用
return
a + b;
};
}
return self ;
}
- ( void )fun {
int c = _variableName ( 1 , 2 ); //block 被调用,即执行上面定义的代码 a+b
};
}
return self ;
}
- ( void )fun {
int c = _variableName ( 1 , 2 ); //block 被调用,即执行上面定义的代码 a+b
}
@end
声明定义好一个块之后,就可以像传递一个变量一样,把block里面的代码传递到其他地方运行。这是块其中一个强大之处。
3.捕获自由变量
块另外一个强大之处是:在声明它的范围里,所有变量都可以为其捕获。例2
@implementation
EOCClass
- ( id )init {
if (( self = [ super init ])) {
int additional = 5 ;
_variableName = ^( int a, int b) { // 定义一个块
return a + b + additional; //additional 被块捕获
- ( id )init {
if (( self = [ super init ])) {
int additional = 5 ;
_variableName = ^( int a, int b) { // 定义一个块
return a + b + additional; //additional 被块捕获
};
}
return self ;
}
- ( void )fun {
int c = _variableName ( 1 , 2 );
return self ;
}
- ( void )fun {
int c = _variableName ( 1 , 2 );
}
在fun方法调用块变量_variableName,这个块变量却能访问了声明在init方法里面的additional,这是因为块在声明时捕获了additional。需要注意的是,如果实例变量被块捕获了,那么也会自动把self一并捕获了,因为在对象内直接访问实例变量和通过self来访问是等效的:
self->anInstanceVariable = @“something”;
__block修饰符
默认情况下,为块所捕获的变量,是不可以在块里修改的。在上例中,假如块内的代码改动了additional的值,那么编译器就会报错。在声明变量时加上__block修饰符,这个变量就可以在块内修改了。
__block
int
additional =
5
;
_variableName
= ^(
int
a,
int
b) {
//
定义一个块
additional = 3 ;
return a + b + additional; //additional 被块捕获
additional = 3 ;
return a + b + additional; //additional 被块捕获
};
注意:以下情况变量不需加修饰符__block也能在块修改。
1.静态变量
2.全局变量
3.实例变量
另外,块所捕获的变量的值,是块定义时变量的值,如果块定义完之后再修改变量,块内变量值仍是修改前的。但若变量是静态变量、全局变量、实例变量或__block修饰的变量,在块的值是其最新的值。(来源:
http://www.cnblogs.com/kesalin/archive/2013/04/30/ios_block.html)
- (
void
)testAccessVariable
{
NSInteger
outsideVariable =
10
;
//__block NSInteger outsideVariable = 10;
NSMutableArray
* outsideArray = [[
NSMutableArray
alloc
]
init
];
void (^blockObject)( void ) = ^( void ){
NSInteger insideVariable = 20 ;
NSLog ( @" > member variable = %lu" , ( unsigned long ) self . memberVarible );
NSLog ( @" > outside variable = %ld" , ( long )outsideVariable);
NSLog ( @" > inside variable = %ld" , ( long )insideVariable);
[outsideArray addObject : @"AddedInsideBlock" ];
};
outsideVariable = 30 ;
self . memberVarible = 30 ;
blockObject();
NSLog ( @" > %lu items in outsideArray" , ( unsigned long )[outsideArray count ]);
void (^blockObject)( void ) = ^( void ){
NSInteger insideVariable = 20 ;
NSLog ( @" > member variable = %lu" , ( unsigned long ) self . memberVarible );
NSLog ( @" > outside variable = %ld" , ( long )outsideVariable);
NSLog ( @" > inside variable = %ld" , ( long )insideVariable);
[outsideArray addObject : @"AddedInsideBlock" ];
};
outsideVariable = 30 ;
self . memberVarible = 30 ;
blockObject();
NSLog ( @" > %lu items in outsideArray" , ( unsigned long )[outsideArray count ]);
}
输出结果为:
> member variable =
30
> outside variable = 10
> inside variable = 20
> 1 items in outsideArray
> outside variable = 10
> inside variable = 20
> 1 items in outsideArray
在块内
outsideArray添加了一个字符串,因为并不是改变
outsideArray本身。
4.块的内部结构
块本身也是对象:
isa:指向Class对象的指针,即指向块所属的类。
invoke:是一个函数指针,指向块的实现代码。
descriptor:是指向结构体的指针,其中声明了块对象的大小,还声明了copy与dispose这两个函数所对应的函数指针。函数会在拷贝及丢弃块对象时运行。
块还会把它所捕获的所有变量都拷贝一份,拷贝的并不是对象本身,而是指向这些对象的指针变量。这是块为什么能访问自由变量的原因。
5.全局块、栈块及堆块
5.1栈块NSConcreteStackBlock
定义块的时候,其所占得内存区域是分配在栈中,这就是说,块只在定义它的那个范围内有效。例如,下面这段代码就有危险:
void (^block)( );
if(/* some condition */) {
block = ^{
NSLog(@“block A”);
};
} else {
block = ^{
NSLog(@“block B”);
}
block( );
编译器会给每个块分配好栈内存,然而等离开了相应了的范围之后,编译器有可能把分配给块的内存覆写掉。于是,这两个块只能保证在对应的if或else语句范围内有效。
5.2堆块NSConcreteMallocBlock
为了在块的作用域之外使用块,需要把块从栈复制到堆。而且,一旦复制到堆上块就成了带引用计数的对象了。如果不再使用这个块,那就应将其释放,在ARC环境下会自动释放,而手动管理引用计数时需要调用release方法。当引用计数将为0后,“分配在堆上的块”会像其他对象,为系统所回收。
上述改进:
void (^block)( );
if(/* some condition */) {
block = [^{
NSLog(@“block A”) copy];
};
5.3全局块NSConcreteGlobalBlock
这种块不会捕获任何变量,运行时也无须任何变量参与。块所使用的整个内存区域,在编译期已经确定了。这种块实际上相当于单例。
void (^block)( ) = ^{
NSLog(@"This is a block”);
}
6.保留环
在例1中的代码稍作修改,使得块捕获self,就会出现保留环。
- (
id
)init {
if (( self = [ super init ])) {
int additional = 5 ;
_variableName = ^( int a, int b) { // 定义一个块
NSLog ( @"%@" , self );
return a + b + additional; //additional 被块捕获
};
}
return self ;
if (( self = [ super init ])) {
int additional = 5 ;
_variableName = ^( int a, int b) { // 定义一个块
NSLog ( @"%@" , self );
return a + b + additional; //additional 被块捕获
};
}
return self ;
}
self被块所捕获,即self被保留了一次。同时块作为self的属性,也被self保留了一次。所以它们互相保留,产生了保留环。使用week声明块属性就可以破除保留环了。
另外一个典型的保留环例子:使用网络获取器EOCNetworkFetcher获取数据,当任务完成时,调用EOCNetworkFetcherCompletionHandler。
#import
<Foundation/Foundation.h>
typedef void (^EOCNetworkFetcherCompletionHandler)( NSData *data);
@interface EOCNetworkFetcher : NSObject
@property ( nonatomic , strong , readonly ) NSURL *url;
- ( id )initWithURL:( NSURL *)url;
- ( void )startWithCompletionHandler:( EOCNetworkFetcherCompletionHandler )completion;
typedef void (^EOCNetworkFetcherCompletionHandler)( NSData *data);
@interface EOCNetworkFetcher : NSObject
@property ( nonatomic , strong , readonly ) NSURL *url;
- ( id )initWithURL:( NSURL *)url;
- ( void )startWithCompletionHandler:( EOCNetworkFetcherCompletionHandler )completion;
@end
#import
"EOCNetworkFetcher.h"
@interface EOCNetworkFetcher ()
@property ( nonatomic , copy ) EOCNetworkFetcherCompletionHandler completionHandler;
@property ( nonatomic , strong ) NSData *downloadedData;
@property ( nonatomic , strong , readwrite ) NSURL *url;
@end
@implementation EOCNetworkFetcher
- ( id )initWithURL:( NSURL *)url {
if (( self = [ super init ])) {
_url = url;
}
return self ;
}
- ( void )startWithCompletionHandler:( EOCNetworkFetcherCompletionHandler )completion {
self . completionHandler = completion;
// 请求执行完后调用 p_requestCompleted
}
- ( void )p_requestCompleted {
if ( _completionHandler ) {
_completionHandler ( _downloadedData );
}
}
@interface EOCNetworkFetcher ()
@property ( nonatomic , copy ) EOCNetworkFetcherCompletionHandler completionHandler;
@property ( nonatomic , strong ) NSData *downloadedData;
@property ( nonatomic , strong , readwrite ) NSURL *url;
@end
@implementation EOCNetworkFetcher
- ( id )initWithURL:( NSURL *)url {
if (( self = [ super init ])) {
_url = url;
}
return self ;
}
- ( void )startWithCompletionHandler:( EOCNetworkFetcherCompletionHandler )completion {
self . completionHandler = completion;
// 请求执行完后调用 p_requestCompleted
}
- ( void )p_requestCompleted {
if ( _completionHandler ) {
_completionHandler ( _downloadedData );
}
}
@end
某个类会创建这种网络数据获取器对象,并用其从URL中下载数据:
@implementation
EOCClass {
EOCNetworkFetcher *_networkFetcher;
NSData *_fetchedData;
EOCNetworkFetcher *_networkFetcher;
NSData *_fetchedData;
}
- (
void
)downloadData {
NSURL *url = [[ NSURL alloc ] initWithString : @"www.example.com/something.dat" ];
_networkFetcher = [[ EOCNetworkFetcher alloc ] initWithURL :url];
[ _networkFetcher startWithCompletionHandler :^( NSData *data) {
NSLog ( @"Request URL %@ finished" , _networkFetcher . url );
_fetchedData = data;
}];
NSURL *url = [[ NSURL alloc ] initWithString : @"www.example.com/something.dat" ];
_networkFetcher = [[ EOCNetworkFetcher alloc ] initWithURL :url];
[ _networkFetcher startWithCompletionHandler :^( NSData *data) {
NSLog ( @"Request URL %@ finished" , _networkFetcher . url );
_fetchedData = data;
}];
}
@end
因为handler要设置_fetchedData实例变量,所以它必须捕获self变量,handler块保留了创建网络获取器的那个EOCClass实例。而EOCClass实例则通过strong实例变量保留了获取器,最后获取器对象又保留了handler块。
打破保留环:要么令_networkFetcher实例变量不再引用获取器,要么令获取器的completionHandler属性不再持有handler块。
- (
void
)downloadData {
NSURL *url = [[ NSURL alloc ] initWithString : @"www.example.com/something.dat" ];
_networkFetcher = [[ EOCNetworkFetcher alloc ] initWithURL :url];
[ _networkFetcher startWithCompletionHandler :^( NSData *data) {
NSLog ( @"Request URL %@ finished" , _networkFetcher . url );
NSURL *url = [[ NSURL alloc ] initWithString : @"www.example.com/something.dat" ];
_networkFetcher = [[ EOCNetworkFetcher alloc ] initWithURL :url];
[ _networkFetcher startWithCompletionHandler :^( NSData *data) {
NSLog ( @"Request URL %@ finished" , _networkFetcher . url );
_fetchedData = data;
_networkFetcher = nil; //不推荐使用这种方法,它责任推给API调用者了
}];
}
或
- (
void
)p_requestCompleted {
if ( _completionHandler ) {
_completionHandler ( _downloadedData );
}
self . completionHandler = nil ;
if ( _completionHandler ) {
_completionHandler ( _downloadedData );
}
self . completionHandler = nil ;
}
总结:
1.块是一个可以访问自由变量的匿名函数。
2.它的一个强大之处是,声明定义好一个变量之后,可以作为参数传递块,那么块内的代码就可以传递到其他地方运行了。
3.块另外一个强大之处是,它可以捕获在它声明范围内的所有变量,需要注意的是,如果在块内捕获实例变量也会把self一并捕获,因为在对象内直接访问实例变量和通过self访问是等效的,这容易导致保留环。
4.默认情况下,所捕获的变量是只读的,如果想在块内修改所捕获的变量,需要在声明变量时加上__block修饰符。但静态变量、全局变量、实例变量不需要加__block修饰符,也能在块修改(跟作用域有关)。
5.块是一个轻量级的结构体,在块的内部第一个变量是isa,指向块所属的类。还有一个函数指针,指向块的实现代码。这是块为什么能被当做变量传递的原因。这个结构还保存着块所捕获的变量,这是块为什么能访问自由变量的原因。
6.块有栈块、堆块及全局块。在定义块时,块所占内存是分配在栈内的,也就是说,离开了定义块所在范围后,块就被释放了。若要想在定义块的范围之外使用块,需要把块从栈中拷贝到堆中,这时,块就变成了带有引用计数的对象了。不使用块时,需要释放块。在ARC环境下会自动释放。
7.使用块需要注意的是,保留环,比如把块声明为属性,self又被块所捕获,这时self跟块就互相保留了,导致内存泄露。