blocks是OS X Snow Leopard和iOS4引入的C语言扩充语法,其优点在于代码简洁而且集中,而且还可以写匿名函数!
返回类型 (^块名称)(参数列表)
int (^addBlock)(int a, int b);
^ ( 返回值类型 ) (参数列表) (表达式)
^int (int count) {return count + 1;}
^ (参数列表) (表达式)
^ (int count) {return count + 1;}
^ (表达式)
^ {printf("Block\n");}
block也可以定义称为block类型变量,一般可以有以下用途:
自动变量
函数变量
静态变量
静态全局变量
全局变量
//定义block变量
int (^blk) (int);
//给block变量赋值
int (^blk) (int) = ^ (int count) {return count + 1;};
int (^blk1)(int) = blk;
int (^blk2)(int);
blk2 = blk1;
//block变量当做参数传递
void func(int (^blk) (int)){/处理/}
//block变量当做返回值
int (^func()(int))
{
return ^(int count) {return count + 1;};
}
//typedef定义能够让代码更加清晰易懂
typedef int (^blk_t)(int);
void func(blk_t blk){/处理/};
blk_t func(){/处理/}
block在语法定义的时候,block表达式能够截获所使用的自动变量的值,而且在block语法后改变自动变量的值,block中的自动变量的值也不会受到影响。
int main()
{
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{printf(fmt, val);};
val = 2;
fmt = "There values were changed, val = %d\n";
blk();
return 0;
}
//输出
val = 10;
在block中,你不能够修改截获的自动变量的值,否则回产生编译错误,如果真的想修改自动变量的值,那么需要用__block来修饰自动变量的值。
//错误使用
int val = 0;
void (^blk) (void) = ^{val = 1;};
blk();//编译出错
//正确使用
__block int val = 0;
void (^blk) (void) = ^{val = 1;};
blk();
如果截获objective-c对象,操作对象时合法的,但是对对象赋值是非法的。
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
id obj = [[NSObject alloc] init];
[array addObject: obj];
//array = [[NSMutableArray alloc] init]; //编译出错
}
使用C语言的字符串字面量数组,虽然没有像截获的自动变量赋值,但是也会出现编译错误
//错误的使用方法
const char text[] = "hello";
void (^blk)(void) = ^{
printf("%c\n", text[2]);//编译出错
}
//正确的使用方法
const char *text = "hello";
void (^blk)(void) = ^{
printf("%c\n", text[2]);//编译出错
}
其实block和__block变量的实质都是对象,如下:
名称
实质
block
栈上block的结构体实例
__block变量
栈上__block变量的结构体实例
block有三种类型的存储域,分别是:
_NSConcreteStackBlock
_NSConcreteGlobalBlock
_NSConcreteMallocBlock
//栈Block,定义在函数之内,系统自动回收
-(void)block{
void (^stackBlock) () = ^{
NSLog(@"this is a block");
};
}
//堆Block,定义在函数之内,引用计数加1,非ARC环境由开发者释放
-(void)block{
void (^stackBlock) () = [^{
NSLog(@"this is a block");
} copy];
}
//全局Block,定义在函数之外,相当于一个单例
void (^globalBlock) () = ^{
NSLog(@"this is a block");
};
对应的存储区域分别如下:
block在语法记述时,将block保存在栈上,当block语法记述的变量作用域结束,栈上的block也会因没有持有者而被释放掉,如下:
那么问题来了:
原来block会在以下情况调用copy函数将栈上的block复制到堆上,使得即使超出了作用域,栈上的block被释放了,但是仍然可以访问堆上的block
那么对属于不同存储域的block进行copy,会产生什么效果呢?总结如下:
Block的类
副本源的配置存储域
复制效果
_NSConcreteStackBlock
栈
从复制到堆
_NSConcreteGlobalBlock
程序的数据区域
什么也不做
_NSConcreteMallocBlock
堆
引用计数增加
可见,不管block配置在何处,对其使用copy方法复制都不会引起任何问题,因此为了避免栈上的block被释放导致访问出错,应该使用copy方法将block复制到堆上,在不确定的时候调用copy方法即可,不过这个过程相当消耗CPU时间,因此谨慎对待。
除此之外,block中如果引用了objective-c对象变量,那么记录在block对象类(编译源码会有block类)中的对应对象会用__strong来修饰,也就是说block持有该对象。这也就是为什么会出现下面代码输出的原因:
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj){
[array addObject: obj];
NSLog(@"array count = %ld", [array count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
//输出
array count = 1
array count = 2
array count = 3
但是假如block中如果引用了objective-c对象变量,但是不持有该对象会出现什么情况?我们用__weak来修饰block中对象变量,代码如下。__strong 修饰的变量array超出作用域后被释放、废弃,此时__weak修饰的变量array2被置nil。因此输出结果都是0。
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
id __weak array2 = array;
blk = [^(id obj){
[array2 addObject: obj];
NSLog(@"array2 count = %ld", [array2 count]);
} copy];
}
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
//输出
array2 count = 0
array2 count = 0
array2 count = 0
当block从栈复制到堆时,使用的所有__block变量也必定配置栈上,而这些变量也全部被复制到堆上,并且被堆上的block持有。
__block 变量的配置存储域
Block从栈复制到堆时的影响
栈
从栈复制到堆并被Block持有
堆
被Block持有
__block变量的复制情况如下图:
当超出block语法作用域,栈上的block和__block变量因不被持有而被释放,当超出block变量的作用域,堆上的block和__block变量因不被持有而被释放,当释放时,会调用block的dispose函数,具体如下图:
__block变量在没有复制到堆上时,__block变量结构体中的__forwarding指针指向自己,当__block变量复制到堆上后,栈上的__block变量结构体中的__forwarding指针指向堆上__block变量结构体中的__forwarding指针,堆上的__block变量结构体中的__forwarding指针指向它自己,总的来说,使用__forwarding指针是为了无论在block语法中、block语法外使用__block变量,还是__block变量配置在栈上火堆上,都可以顺利的访问同一个__block变量,如下图:
如果在block中使用附有__strong修饰符的对象类型自动变量,那么当block从栈复制到堆时,该对象为block所持有。酱紫容易引起循环引用。
typedef void (^blk_t) (void);
@interface MyObject: NSObject
{
blk_t blk_;
}
@implementation MyObject
-(id)init
{
self = [super init];
blk_ = ^{NSLog(@"self = %@", self);};
return self;
}
-(void)dealloc
{
NSLog(@"dealloc");
}
@end
int main()
{
id o = [[MyObject alloc] init];
NSLog(@"%@", o);
return 0;
}
如上代码所示,MyObject类对象对Block类型成员变量blk_持有强引用,同时init实例方法中,Block类型成员blk_对id类型变量self持有强引用,并且由于Block语法赋值在成员变量blk_中,因此通过Block语法生成的栈上的block由栈复制到了堆上,此时堆上的self持有block,block持有self,导致循环引用,如下图。
那么要如何解决这种情况?两种方法
方法一:用__weak(iOS4,Snow Leopard的应用程序中需要使用__unsafe_unretained修饰符来代替)修饰符修饰变量,且都不必担心产生悬挂指针问题。
-(id)init
{
self = [super init];
id __weak tmp = self;
//id __unsafe_unretained tmp = self;
blk_ = ^{NSLog(@"self = %@", tmp);};
return self;
}
方法二:也可以使用__block变量来避免循环引用,但是一定要调用execBlock实例方法,不然会引起循环应用导致内存泄露。
typedef void (^blk_t) (void);
@interface MyObject: NSObject
{
blk_t blk_;
}
@implementation MyObject
-(id)init
{
self = [super init];
__block id tmp = self;
blk_ = ^{
NSLog(@"self = %@", tmp);
tmp = nil;
};
return self;
}
-(void)execBlock
{
blk_();
}
-(void)dealloc
{
NSLog(@"dealloc");
}
@end
int main()
{
id o = [[MyObject alloc] init];
[o execBlock];
return 0;
}
以上代码引起循环泄露的原因,如下图:
执行了execBlock会使得nil被赋值在__block变量tmp中,使得__block变量tmp对Myobject类对象的强引用失效,从而避免了循环引用,如下图:
总的来说,使用__block变量避免循环引用的方法有如下优点:
通过__block变量可控制对象的持有期间
在不能使用__weak修饰符的环境中不使用__unsafe_unretained修饰符即可(不用担心悬挂指针问题)
在执行Block时可动态的决定是否将nil或其他对象赋值在__block变量中
不足之处在于为了避免循环引用必须执行Block
手机扫一扫
移动阅读更方便
你可能感兴趣的文章