Block本质
Block本质上也是一个OC对象,他内部也有一个isa指针。block是封装了函数调用以及函数调用环境的OC对象。
闭包 = 一个函数「或指向函数的指针」+ 该函数执行的外部的上下文变量「也就是自由变量」;Block是Object-C对于闭包的实现
- 可以嵌套定义,定义block方法和定义函数方法相似
- Block可以定义在方法内部或外部
- 只有调用Block时候,才会执行其{}体内的代码
- 本质是对象,使代码高聚合
一、Block的实现
1、无参数无返回值
// 1、无参数,无返回值,声明和定义
void(^ZSBlockOne)(void) = ^(void){
NSLog(@"无参数,无返回值")
};
ZSBlockOne();
2、有参数无返回值
// 2、有参数,无返回值,声明和定义
void(^ZSBlockTwo)(int value) = ^(int value){
NSLog(@"有参数,无返回值的Block:%d",value);
};
ZSBlockTwo()
3、有参数有返回值
// 3、有参数,有返回值
int(^ZSBlockThree)(int value) = ^(int value){
NSLog(@"有参数,有返回值的block:%d",value);
return value + 10;
};
int a = ZSBlockThree(5);
4、无参数有返回值
// 4、无参数,有返回值
int(^ZSBlockFour)(void) = ^{
NSLog(@"无参数,有返回值的block");
return 100;
};
int a = ZSBlockFour();
5、开发中常用typedef定义block
typedef void (^ZSBlock)(int value);
// ZSBlock是一个种block类型,之后可以直接创建属性
@property(nonatomic, copy) ZSBlock myBlock;
self.myBlock = ^(int value){
NSLog(@"调用了block,传了一个值为:%d",value);
};
self.myBlock(10);
二、Block的类型
Block共有3种类型:
1、全局Block(NSGlobalBlock)
2、栈上的Block(NSStackBlock)
3、堆上的Block(NSMallocBlock)
通过代码看下他们的作用域以及使用场景
// 1、Block内部没有调用外部变量
void(^block)(void) = ^{
NSLog(@"hello");
};
// 2、内部调用外部变量的block
int a = 10;
void(^block1)(void) = ^{
NSLog(@"a的值为:%d",a);
};
// 3、直接调用block的class
NSLog(@"%@ %@ %@",[block class],[block1 class],[^{
NSLog(@"%d",a);
} class]);
运行结果
2019-09-04 13:48:51.981040+0800 ZSLoadDemo[93842:995366] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
结论
1、NSGlobalBlock没有访问auto变量,直到程序结束才会被回收。
2、NSStackBlock访问了auto(外部)变量,存放在栈中,我们知道栈中的内存由系统自由分配和释放,作用域执行完毕之后就会被立即释放。
3、NSMallocBlock 本质NSStackBlock调用了copy,存放于堆内存中,是一个带引用计数的对象,也是我们平常开发中最常用的。存放在堆区需要我们进行内存管理。
总结:存储在栈中的Block就是栈块,存储在堆中就是堆块,既不在栈中,也不再堆中就是全局块
三、Block与外部变量
参考文献:https://imlifengfeng.github.io/article/457/
为了保证block内部能够正常访问外部的变量,block有一个变量捕获机制
1、block使用局部变量值
(1)默认情况,block内部直接使用外部局部变量
对于block外的变量引用,block默认是将其复制到其数据结构中来访问的。也就是说block的自动变量截获值针对block内部使用的自动变量,不实用则不截获,因为解惑的自动变量会存储在block的结构体内部,会导致block体积变大。所以要注意默认情况下block只能访问不能修改局部变量的值。
代码实例
int value = 10;
void(^myBlock)(void) = ^{
NSLog(@"value==%d",value);
};
value = 20;
myBlock();
//输出结果
age = 10;
(2)使用__block 修饰的外部变量
对于用__block修饰的外部变量引用,block是复制其引用地址来实现访问的。block内部可以修改__block修饰的外部变量的值。
代码实例
__block int value = 10;
void(^myBlock)(void) = ^{
NSLog(@"value==%d",value);
};
value = 20;
myBlock();
//输出结果
age = 20;
四、block为什么用copy修饰
上面说过block是一个oc对象,所以block理论上是可以用retain或strong去描述的。但是block在创建的时候它的内存是默认是分配栈(stack)上,而不是堆(heap)上的。所以它的作用域仅限创建时候的当前上下文(函数,方法…),当你在该作用域外使用block 时,程序会崩溃。
- 一般情况下你不需要自行调用copy或则retain一个block。只有当你需要在block定义域以外的地方使用时才需要copy,Copy将block从内存栈区移到堆区。
- 其实block使用copy是MRC留下来的也算是一个传统吧,在MRC下,如上述在方法中的block创建在栈区,使用copy就能把他放到堆区,这样在作用域外调用该block程序就不会崩溃。
- 但在ARC下,使用copy与strong其实都一样,因为block的retain就是用copy来实现的。
五、关于Block面试题
1、Copy将block从内存栈区移到堆区,这里移的是原来的block,还是新生成的block对象?
拷贝栈区的block所在的内存,然后在堆区重新开辟一块放新生成的block
2、__block什么时候用?
1、当需要在block中修改外部变量值时使用
2、外部变量避免被block强引用一次,避免循环引用
3、在block里面堆数组执行添加操作,这个数组需要声明为__block吗?
不需要,因为数组可以理解为指针,在block里面堆数组进行添加,删除操作,只是改变了指针指向的值,而没有修改外部数组的地址。
4、在block里面对NSInteger进行修改,需要对NSInteger声明为__block?
需要,NSInteger是基本数据数据类型,基本数据类型没有static等的保护下,需要__block。
5、为什么使用__block修饰外部变量的值就可以被block修改?
内部实现代码:
__block int val = 10;
转换成
__Block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
};
会发现一个局部变量加上**__block修饰符后竟然跟block一样变成了一个__Block_byref_val_0结构体类型的自动变量实例。
我们在block内部访问val变量则需要通过一个叫forwarding的成员变量来间接访问val变量**
通过forwarding无论是在block中还是 block外访问block变量, 也不管该变量在栈上或堆上, 都能顺利地访问同一个__block变量。
未完待续,发现有问题可以留言一起讨论下