关于Block:
在我们使用OC进行iOS开发和Mac OS开发中,Block语法是我们最常见的语法之一,而且苹果官方也推荐我们使用Block代码块的语法,如果仔细观察,大家可以看到,在近期苹果新开放的系统框架的API中,苹果越来越多的使用了Block这种语法方式,而不是@selector方法选择器.
苹果如此推荐Block的使用,以及这种语法深受广大OC开发者的喜爱的原因是Block不可替代的优点:Block的写法非常简洁高效,而且可以减少代码的分散,提高内聚性等等,虽然代理等回调方式也有它的优点,但是广大OC开发者确大都对Block情有独钟.
接下来,我们一起来看看Block的用法,原理,以及注意点吧.
初识Block:
Block 是在OSX 10.4 以及iOS4 之后引入的新的语法形式,从技术上来讲,Block应该属于C语言的特性,所以只要编译器支持,就可以在C,C++,OC,OC++中使用.
通俗点来说Block的内容其实就是一个代码块,表示一段代码.但是这个代码块可以有返回值,可以有参数
Block也可以看成是一个OC对象,所以它也能赋值给变量.
没有参数,没有返回值的Block
说一百句话也不如上一句代码,我们先来写个最简单的Block吧:
^{
NSLog(@"I am a Block");
};
很简单的语法形式对吧,一对{}前面再加上一个^,就是最简单的Block形式,这个Block只有一句代码,没有返回值,也没有参数.
有参数,没有返回值的Block
^(NSInteger count,NSString *str){
NSLog(@"%ld---%@",count,str);
};
这个Block带了两个参数分别是count和str,参数就写在^后面的()中就可以了
有参数,有返回值的Block
^(NSInteger count,NSString *str){
NSLog(@"%ld---%@",count,str);
return YES;
};
这个Block不仅有两个参数,还有返回值,返回值的类型是一个BOOL值
这些就是几种Block的类型啦,是不是也没有那么复杂.
把Block赋值给变量
上面我们创建了三种类型的Block,但是,我们却无法方便的拿来使用.这个时候,我们需要把这个创建出来的Block赋值给一个变量:
BOOL (^aBlock)(NSInteger count,NSString *str)=^(NSInteger count,NSString *str){
NSLog(@"%ld---%@",count,str);
return YES;
};
我们把上面那个既有返回值又有参数的Block赋值给了一个叫aBlock的变量,这个变量名是写在中间的看起来有点奇怪,但是熟悉了Block的语法就会习惯了,而BOOL (^aBlock)(NSInteger count,NSString str)表示Block的类型.BOOL表示返回值,(NSInteger count,NSString str)表示这个Block的两个参数.
通过变量名调用Block
我们如果需要调用这个定义的Block,则可以通过变量名如这样:
aBlock(10,@"123");
这样我们就调用了这个Block,并为这个Block传入了两个参数.
内联Block
我们使用Block经常是把Block当做一个参数传出一个方法中,这种用法叫做内联Block.
我们直观的从代码上了解这种用法就好了:
-(void)aMethodWithInlineBlock:(BOOL(^)(NSString *,NSInteger ))block
{
if (block) {
block(@"123",10);
}
}
上面为一个带Block参数的方法实现,下面是调用这个方法的代码:
[self aMethodWithInlineBlock:^BOOL(NSString *str,NSInteger count) {
NSLog(@"%@----%ld",str,count);
return YES;
}];
Block最大的特性:捕获变量:
在Block的诸多特性中,最显著的最强大的特性是可以在Block的声明范围中,捕获需要的变量.实际上,我们可以把Block看做是一个OC对象,所以它也会用引用计数来管理,如果我们在Block中使用了某个外部变量,而该变量是对象类型的话,Block也会对这个变量有一个强引用,当Block本身的引用计数变为0的时候,系统会释放Block,Block也会对所捕获的对象做一次release操作.
Block内存结构:
类型 | 变量名 |
---|---|
void * | isa |
int | flag |
int | reserved |
void(*)(void) | invoke |
struct | descriptor |
捕获到的变量 | 捕获到的变量 |
这其中最重要的是invoke这个函数指针,它指向的是Block的实现代码.
descriptor是一个结构体,这个结构体中有Block的大小,还有copy,dispose这两个函数指针,分别是在拷贝和丢弃Block的时候调用的.
Block会对它捕获到的变量指针做一个拷贝.
在栈中的Block
我们定义Block时,Block所占据的其实是栈内存空间,在栈内存空间中,那么我们就无法控制Block的生命周期,ARC也没有办法对这种Block进行引用计数的内存管理,这种Block通常是不安全的,例如:
void (^aBlock)();
if (self.condition) {
aBlock=^{
NSLog(@"123");
};
}else
{
aBlock=^{
NSLog(@"456");
};
}
aBlock();
这段代码其实是不安全的,因为我们定义出来的Block在栈内存中,而我们不能保证在出了Block的定义区域后,这个Block还有效,那么在这种情况下,赋值给aBlock变量是不安全的,然而,我们只要对Block进行一次copy操作,就能在堆内存中拷贝一份Block,这样我们就能对Block的内存进行控制了,ARC也能对copy出来的Block进行内存管理了.
上面的代码如下改就变得安全了:
void (^aBlock)();
if (self.condition) {
aBlock=[^{
NSLog(@"123");
} copy];
}else
{
aBlock=[^{
NSLog(@"456");
}copy ];
}
aBlock();
为Block变量创建别名
我们上面说到Block变量名是写在类型中间的,看起来似乎有点奇怪,而且也不是非常有利于易读性,所以,我们一般会为Block类型起一个别名.
之前我们对一个Block变量赋值为这种写法:
BOOL (^aBlock)(NSInteger ,NSString *)=^(NSInteger count,NSString *str){
NSLog(@"%ld---%@",count,str);
return YES;
};
aBlock(10,@"123");
我们现在为这种类型的Block起个别名
typedef BOOL (^ABlock)(NSInteger ,NSString *);
ABlock为这种Block的类型名,我们之后再赋值给这种类型的Block就可以如下写了:
ABlock aBlock=^(NSInteger count,NSString *str){
NSLog(@"%ld---%@",count,str);
return YES;
};
aBlock(10,@"123");
使用Block最大的注意点:避免循环引用
我们刚刚有说过,在Block中,Block会自动捕获要使用到的变量,并持有该变量.这时往往容易出现循环引用的问题,当前类实例持有该Block,该Block又在其中使用self,持有了该实例,这时循环引用问题就出现了.
例如:我们在当前类声明一个Block属性:
@property (nonatomic,copy)ABlock ablock;
我们给这个Block赋值:
self.ablock=^(NSInteger count,NSString *str ){
self.view.backgroundColor=[UIColor greenColor];
return YES;
};
这样,由于我们在Block中使用了self,那么这个Block就会把当前实例捕获,并持有,而当前实例又拥有该Block,这就导致了循环引用的状况.
如下修改:
__weak ViewController * weakSelf=self;
self.ablock=^(NSInteger count,NSString *str ){
weakSelf.view.backgroundColor=[UIColor greenColor];
return YES;
};
我们在该Block中使用了self的若引用,从而打破了这个引用环,解决了循环引用的问题.
特别需要注意的一种情况是对在Block中使用成员变量,引起的循环引用问题容易让开发者们所忽视,因为,在Block中虽然并没有使用self,而是使用的成员变量,但是想要捕获成员变量又一定要捕获self,这依然会导致循环引用.例如:
//定义了一个成员变量
{
NSString *_chengyuanbianliang;
}
//在Block中使用成员变量,导致循环引用
self.ablock=^(NSInteger count,NSString *str ){
NSLog(@"%@",_chengyuanbianliang);
return YES;
};
我们可以如下解决这个问题:
__weak ViewController * weakSelf=self;
self.ablock=^(NSInteger count,NSString *str ){
__strong ViewController *strongSelf=weakSelf;
NSLog(@"%@",strongSelf->_chengyuanbianliang );
return YES;
};