Block:带有自动变量的匿名函数。
匿名函数:没有函数名的函数,一对{}包裹的内容是匿名函数的作用域。
自动变量:关于“带有自动变量(局部变量)”的含义,这是因为Block拥有捕获外部变量的功能。在Block中访问一个外部的局部变量,Block会持用它的临时(瞬时)状态,自动捕获变量值,外部局部变量的变化不会影响它的的状态。
Block的作用:用来保存某一段代码,可以在恰当时候再去调用,功能类似于函数和方法。
Block标准声明与定义
//return_type表示返回的对象(可以是void,并省略)
//blockName表示block的名称
//var_type表示参数的类型(可以是void,并省略)
//varName表示参数名称
标准声明与定义:
return_type (^blockName)(var_type...) = ^ return_type (var_type varName...) {
// ...
};
blockName(var...);
当返回类型为空,可以省略为:
void (^blockName)(var_type...) = ^ (var_type varName...) {
// ...
};
blockName(var...);
当参数类型为空,可以省略为:
return_type (^blockName)(void) = ^ return_type {
// ...
};
blockName();
当返回类型和参数类型都为空,可以省略为:
void (^blockName)(void) = ^{
// ...
};
blockName();
typedef简化Block的声明:
typedef return_type (^blockName) (var_type...);
block作为属性,例子:
@property (copy) void(^MyBlock)(void);
typedef void(^MyBlock)(void);
@property (nonatomic, copy) MyBlock block;
Block类型变量作为函数的参数时,例子:
- (void)func:(int(^)(int))blk {
NSLog(@"Param:%@", blk);
}
typedef int(^MyBlock)(int);
- (void)func:(MyBlock)blk {
NSLog(@"Param:%@", blk);
}
block对变量的捕获
1:可以捕获不可以修改变量
局部变量
2:可以捕获且可以修改变量
静态局部变量
__block修饰的局部变量
全局变量和静态全局变量
1. 局部变量为什么可以被捕获确不能修改
int a = 10;
void(^blcok)(void) = ^{
NSLog(@"%d",a);
};
a=20;
blcok();
// log : a = 10
我们在block内部获取的a都是定义的时候传进来的值,这也就导致为什么block可以捕获局部变量却不可以修改的原因。
自动变量(局部变量)值为一个对象情况:
当自动变量(局部变量)为一个类的对象,且没有使用__block修饰时,虽然不可以在Block内对该变量进行重新赋值,但可以修改该对象的属性。如果该对象是个Mutable的对象,例如NSMutableArray,则还可以在Block内对NSMutableArray进行元素的增删:
NSMutableArray * array = [[NSMutableArray alloc] initWithObjects:@"1",@"2",nil];
NSLog(@"Array Count:%ld", array.count); //打印Array Count:2
void(^blk)(void) = ^{
[array removeObjectAtIndex:0];
//array = [NSNSMutableArray new];//没有__block修饰,编译失败!
};
blk();
NSLog(@"Array Count:%ld", array.count);//打印Array Count:1
2 全局变量和静态全局变量可以被捕获也可以修改
@property (nonatomic, assign) int a;
-(void)blockTest
{
void(^blcok)() = ^{
NSLog(@"%d",a);
} ;
a = 20;
blcok();
// log: a : 20
}
全局变量和静态全局变量在执行Block语法的时候,它们被Block捕获进去,这一点很好理解,因为是全局的,作用域很广,所以Block捕获了它们进去之后,在Block里面进行++操作,Block结束之后,它们的值依旧可以得以保存下来。
3 静态局部变量可以被捕获也可以修改
- (void)blockTest
{
static int a = 10;
void(^blcok)() = ^{
NSLog(@"%d",a);
};
a = 20;
blcok();
//log : a:20
}
静态局部变量传递给Block是内存地址值,所以能在Block里面直接改变值。在执行Block语法的时候,Block语法表达式所使用的静态局部变量的地址是被保存进了Block的结构体实例中,也就是Block自身中。所以能够在Block 内部修改静态局部变量的值。
4 __block修饰的变量 可以被捕获也可以修改
- (void)blockTest
{
__block int a = 10;
void(^blcok)() = ^{
NSLog(@"%d",a);
};
a = 20;
blcok();
// log :a: 20
}
在变量的前面加上了 _ block之后,这是因为做的引用传递(也就是指针传递),而不加 _block的变量,就相当于值传递,所以在内部修改是不能改变外部变量的值的。
声明block属性的时候为什么用copy?
其实block有3种类型:
全局块(_NSConcreteGlobalBlock):block不访问外界变量(包括栈中和堆中的变量)
block既不在栈中也不在堆中,此时就为全局块。
栈块(_NSConcreteStackBlock):栈块存储在栈区,超出作用域则马上被销毁。
堆块(_NSConcreteMallocBlock):堆块存储在堆区中,是一个带引用计数的对象,需要自行管理其内存。ARC环境下,访问外界变量的block默认存放在堆中,实际上是先放在栈区,在ARC情况下自动又拷贝到堆区,自动释放。
使用copy修饰符的作用就是将block从栈区拷贝到堆区,我们看下Apple官方文档给出的答案:
You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope.This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior
通过官方文档可以看出,复制到堆区的主要目的就是保存block的状态,延长其生命周期。因为block如果在栈上的话,其所属的变量作用域结束,该block就被释放掉,block中的__block变量也同时被释放掉。为了解决栈块在其变量作用域结束之后被释放掉的问题,我们就需要把block复制到堆中。
不同类型的block使用copy方法的效果也不一样,如下所示:
block使用了外部局部变量,这种情况也正是我们平时所常用的方式。MRC:Block的内存地址显示在栈区,栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区,所以在使用Block属性时使用copy修饰。但是ARC中的Block都会在堆上的,系统会默认对Block进行copy操作,用copy,strong修饰block在ARC和MRC都是可以的,都是在堆区。
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上的几种情况?
1.block作为函数返回值时
2.将block赋值给__strong指针时
3.block作为Cocoa API中方法名含有usingBlock的方法参数时
4.block作为GCD API的方法参数时
如果block在栈空间,不管外部变量是强引用还是弱引用,block都会弱引用访问对象
如果block在堆空间,如果外部强引用,block内部也是强引用;如果外部弱引用,block内部也是弱引用
网友评论