Block知识梳理
1、什么是block
- 将函数和上下文封装起来的对象
/**
* 比如
**/
void (^block)(NSInteger) = ^(NSInteger age){
NSLog(@"%zd",age);
};
block(18);
- 在ViewController.m中编写了上面的block,然后使用(xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名[这里的文件名是ViewController.m])命令clang一下,会发现文件夹中出现xxx.cpp文件,查看文件会有如下代码 :
struct __block_impl {
void *isa;//isa指针
int Flags;
int Reserved;
void *FuncPtr;//函数指针
};
从结构体中可以发现block内部有isa,所以其本质是一个oc对象。
block内部实现为:
static __NSConstantStringImpl __NSConstantStringImpl__var_folders_r4_mlnz1vw53r12b9s528b98m6r0000gn_T_ViewController_d1f10e_mi_0 __attribute__ ((section ("__DATA, __cfstring"))) =
{
__CFConstantStringClassReference,0x000007c8,"%zd",3
};
所以说block是将函数及其上下文封装起来的对象
2、Block变量截获
- 局部变量截获:基本数据类型的局部变量是值截获,引用数据类型的局部变量是指针截获
/**
*基本数据类型的局部变量截获
**/
NSInteger age = 18;
void (^block)(void) = ^(void){
NSInteger tAge = 2 * age;
NSLog(@"base data type----:%zd",tAge);
};
age = 19;
block();
//打印输出的结果:
2021-02-06 12:34:55.706828+0800 DSPracticeDemo[31320:1720535] base data type----:36
/**
*引用数据类型的局部变量截获
*/
NSMutableDictionary *dictage = [NSMutableDictionary dictionary];
[dictage setValue:@(18) forKey:@"age"];
void (^nblock)(void) = ^(void){
NSNumber *qage = [dictage objectForKey:@"age"];
NSInteger tAge = 2 * qage.intValue;
NSLog(@"object data type----: %zd",tAge);
};
[dictage setValue:@(19) forKey:@"age"];
nblock();
//打印输出的结果:
2021-02-06 12:34:55.706949+0800 DSPracticeDemo[31320:1720535] object data type----: 38
- 局部静态变量截获:是指针截获
/**
*局部静态变量截获
**/
static NSInteger ageNum = 18;
void (^sBlock)(void) = ^(void){
NSInteger tAge = 2 * ageNum;
NSLog(@"static data type---:%zd",tAge);
};
ageNum = 19;
sBlock();
//打印输出结果
2021-02-06 12:52:25.678532+0800 DSPracticeDemo[31376:1730406] static data type---:38
- 全局变量、全局静态变量截获:不截获,直接取值
/**
*源码部分
**/
//全局定义变量
NSInteger globalAge = 18;
static NSInteger globalStaticAge = 18;
//方法定义
- (void)blockTest1{
NSInteger baseAge = 18;
static NSInteger staticBaseAge = 18;
__block NSInteger blockBaseAge = 18;
void (^block)(void) = ^(void){
NSLog(@"局部变量 %zd",baseAge);
NSLog(@"局部静态变量 %zd",staticBaseAge);
NSLog(@"局部block变量 %zd",blockBaseAge);
NSLog(@"全局变量 %zd",globalAge);
NSLog(@"全局静态变量 %zd",globalStaticAge);
};
block();
}
同样,clang一下上面的代码,会发现如下代码:可以发现并没有重新定义全局变量、全局静态变量,所以全局变量、全局静态变量在block里是直接取值的
/**
*源码clang成C++后
**/
struct __ViewController__blockTest_block_impl_0 {
struct __block_impl impl;
struct __ViewController__blockTest_block_desc_0* Desc;
NSInteger baseAge;//局部变量
NSInteger *staticBaseAge;//局部静态变量
__Block_byref_blockBaseAge_0 *blockBaseAge; // __block 修饰的局部变量
__ViewController__blockTest_block_impl_0(void *fp, struct __ViewController__blockTest_block_desc_0 *desc, NSInteger _baseAge, NSInteger *_staticBaseAge, __Block_byref_blockBaseAge_0 *_blockBaseAge, int flags=0) : baseAge(_baseAge), staticBaseAge(_staticBaseAge), blockBaseAge(_blockBaseAge->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
3、block的几种形式
- 有全局Block(_NSConcreteBlock)、栈Block(_NSConcreteStackBlock)和堆Block(_NSConcreteMallocBlock)三种形式的Block
- 1、什么是全局block:不使用外部变量的block
//比如
NSLog(@"%@",[^(void){
NSLog(@"global block");
} class]);
//打印输出的结果
2021-02-06 15:06:10.768730+0800 DSPracticeDemo[31560:1778151] __NSGlobalBlock__
- 2、什么是栈block:使用外部变量但未进行copy操作的block
//比如
NSInteger age = 18;
NSLog(@"%@",[^(void){
NSLog(@"stack block:%zd",age);
} class]);
//打印输出的结果
2021-02-06 15:09:52.319949+0800 DSPracticeDemo[31580:1780686] __NSStackBlock__
- 3、什么是堆block:对栈block进行copy操作的为堆block
//比如
NSInteger tage = 18;
void (^block)(void) = ^(void){
NSLog(@"malloc block:%zd",tage);
};
NSLog(@"%@", [block class]);
//打印输出的结果
2021-02-06 15:13:55.833683+0800 DSPracticeDemo[31600:1783269] __NSMallocBlock__
4、总结:
- a、全局block:不使用外部变量的block
- b、栈block:使用外部变量未进行copy操作的block
- c、堆block:对栈block进行copy操作的block
- d、对栈block进行copy会将block拷贝至堆中;对全局block进行copy操作,因为已经初始化了,所以什么也不会做;对堆block进行copy操作,其引用数会增1
- e、另外,对__block修饰的变量进行copy时,由于__forwarding的存在,栈上的__forwarding会指向堆中block,而堆中__forwarding指向自身,所以修改__block修饰的变量,实质上是修改堆中的__block变量;即__forwarding存在的意义是:无论在内存的哪个位置,都可以顺利的访问同一block
5、补充
- block捕获__block的变量会去持有它,如果__block修饰self时,且self持有block,并且block内部使用到__block修饰的self,那么会造成循环引用;即self持有block,block持有__block,__block持有self,出现循环引用,对象无法释放,造成内存泄漏
//比如
__block typeof(self) weakSelf = self;
_block = ^(void){
NSLog(@"%@",weakSelf);
};
_block();
解决方式:在block中使用完weakSelf后手动设置为nil,但是会出现这样一种情况,如果block未被执行,则它们会相互持有(循环引用),所以使用__weak修饰self比较恰当
《如有错误理解,还请各路大神批评指出》