一:基本定义
/*初步上式block定义的一些理解和解释,接下来会详解:
*block名为myBlock,结合C的函数指针,myBlock为block体的指针,指向block体的入口地址
*int result = myBlock(5) <==> ^(int num){return num*num}(5)//将5传给num
*回调时可以将myBlock作为参数传入,也可以直接传入block体^(int num){…};//
*整个block体作为参数传入时,往往没有参数,只是进行延迟运算作用,因为定义了block内容
如果是暂时左值不需要的话,block是不执行的。
例:[request setFailedBlock:^{NSError *error = [request error]; };];
//如果不执行setFailedBlock方法时,后面的block是不执行的,只是静静地待在堆中,原理接下来会说到。
*/
- 我们可以把Block当做Objective-C的匿名函数。Block允许开发者在两个对象之间将任意的语句当做数据进行传递,往往这要比引用定义在别处的函数直观。另外,block的实现具有封闭性(closure),而又能够很容易获取上下文的相关状态信息。
- block是代码块,其本质和变量类似。不同的是代码块存储的数据是一个函数体。使用Block,就可以像其他标准函数一样,传入参数,并得到返回值。
二:关键字__block
主要作用:1.block对外部变量是只读的,要变成可读可写,就需要加上__block
2.将栈中的block复制到堆上一份,从而避免了循环引用这个情况
在Block的代码块里,是不能修改在外面定义的变量,并且在给block赋值的时候,已经对代码块里的变量做了值的拷贝(只读不可修改)。
-
int x = 5; int (^block4)(int) = ^(int y) { int z = x + y; return z; }; NSLog(@"%d,%d",x +=5,block4(5));
打印的值是10,10;
-
- 分析:变量x在Block外定义的,在Block代码块编译的时候,取的x的值为之前的5(不可修改)。因此即使执行x += 5的使x的值变为10,但Block代码块里的x依然是5,所以block(5)的值为5+5=10。
- b: 在变量前添加__block关键字进行修饰后,此变量在Block代码块里的就是可更改的(可读可写),执行代码时取变量最新的值。
-
__block int x = 5; int (^block4)(int) = ^(int y) { int z = x + y; return z; }; NSLog(@"%d,%d",x +=5,block4(5));
打印的值是10,15;
-
-
三:存储类型
block的存储形态有三种:_NSConcretStackBlock(栈)、_NSConcretGlobalBlock(全局)、_NSConcretMallocBlock(堆)
要点一:当block在函数内部,且定义的时候就使用了函数内部的变量,那么这个 block是存储在栈上的。
要点二:当block定义在函数体外面,或者定义在函数体内部且当时函数执行的时候,block体中并没有需要使用函数内部的局部变量时,也就是block在函数执行的时候只是静静地待在一边定义了一下而不使用函数体的内容,那么block将会被编译器存储为全局block。
要点三:全局block存储在堆中,对全局block使用copy操作会返回原函数指针;而对栈中的block使用copy操作,会产生两个不同的block地址,也就是两个匿名函数的入口地址。
要点四:ARC机制优化会将stack的block,转为heap的block进行调用。
下面是自己写的一个例子来验证
typedef void (^myblock) (void);
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
__block int a = 3; //a内存存储在栈区
int *a_d = &a; //指针a_d指向栈区地址
myblock block1 = ^{
NSLog(@"%d",++a);
};
//1.当工程是ARC时,ARC机制优化,在调用block1时,会将栈区内容复制一份到堆区,此时a的内存存储地址在堆区,a的地址改变了,所以++a后a的值为4(堆区),但是指针a_d指向地址的内容还是3(栈区,此时指针a_d指向的地址与a的地址不是一个地址)
//2.当工程是非ARC时,在调用block1时,不会将栈区内容复制一份到堆区,此时a的内存存储地址还是和之前一样(栈区),a的地址不变,++a后a的值为4(栈区),指针a_d指向地址的内容也就是4(指向的地址还是a的地址)
block1();
NSLog(@"%d",*a_d); //当工程是ARC时,此时*a_d等于3,当工程是非ARC时,此时*a_d等于4
//1.当工程时ARC时,[block1 copy],block1在堆区,所以copy后block2与block1是同一个地址
//2.当工程是非ARC时,[block1 copy],block1在栈区,所以copy后block2与block1不是同一个地,单独copy一份地址
myblock block2 = [block1 copy];
block2();
NSLog(@"%d",*a_d);
}
以下存储类题目摘自cocoaChina论坛:
四:作为OC对象的属性,实现对象之间的传值
-
- Block可以看做是一个变量,因此可以作为OC对象的属性
- 需求:在ViewContrler的View上添加按钮,点击按钮模态跳转到FirstViewController视图控制器,FirstViewController的View上又两个按钮,一个是直接dismiss模态跳转到ViewController,一个是更改ViewController的背景色再dismiss模态跳转回ViewController
- 分析:FirstViewController是有ViewController模态推出的视图控制器,在FirstViewController里更改ViewController的背景色,这时就要做到反向传值。
- 实现步骤:
- 1.在FirstViewController.h文件里,定义将block变量作为@property属性
-
// 第一步定义, Block作为property属性 /* void: Block的返回值为空,即无返回值; colorBlock: Block的作为对象属性时的属性名; (UIColor *color): Block的参数是UIColor实例对象 */ @property (nonatomic, copy) void(^colorBlock)(UIColor *color);
-
- 在FirstViewController.m文件里,在按钮点击触发的方法里给block传参,调用block。
-
// 随机生成的颜色 UIColor *color = [UIColor colorWithRed:arc4random()%256/255.0 green:arc4random()%256/255.0 blue:arc4random()%256/255.0 alpha:1]; // 第二步 给Block传入参数color if (self.colorBlock) { self.colorBlock(color); } [self dismissViewControllerAnimated:YES completion:nil];
-
- 在ViewController.m 文件里,实例化FirstViewController对象的地方,执行block代码块。
-
FirstViewController *fvc = [[FirstViewController alloc] init]; // 执行Block代码块 fvc.colorBlock = ^(UIColor *color) { self.view.backgroundColor = color; };
-
- 1.在FirstViewController.h文件里,定义将block变量作为@property属性
- 效果展示: