-
block的本质
-
block的种类及储存区域
-
__block的本质
-
block的循环引用
前言:
这里就不讨论block的具体写法及使用场景了,因为当你有一天想深入了解block 的底层原理时,你早已把block写了几十遍了。
一、block的本质:
block是带有自动变量的匿名函数。
注:
局部变量 = 自动变量(栈区)+ 静态局部变量 (全局区)
这里说的自动变量是指block里面捕获的外部局部变量,当然你也可以不捕获
二、block的种类及储存区域:
NSGlobalBlock :存放在全局区的block
只有当block里面仅仅捕获了外部的静态局部变量、全局变量、静态全局变量时,该block 存. 放在全局区。当然,如果block里面什么类型的外部变量都不捕获时,它也在全局区。
NSStackBlock : 存放在栈区的block
NSMallocBlock : 存放在堆区
在arc下,截获了外部自动变量的block被创建出来时存放在栈区,然后如果该block被strong/copy 修饰符修饰时,系统会将该block从栈区,copy一份到堆区,并将指针指向堆区的block。
而系统默认的就是strong类型(不管是成员变量block还是局部变量block ),所以在大部分情况下解惑了外部自动变量的block都是存放在堆区,只有当你手动的将一个局部block修饰weak属性时,它才不会被copy到堆区。
注:ios的五大内存区域:栈区、堆区、全局区、常量区、代码区
三、__block的本质
先说一下block截获对象的性质
- 局部变量:
- 自动变量:
- 基本类型:截获其值
- 对象类型:截获指针
- 静态局部变量:截获指针
- 自动变量:
- 静态全局变量:截获指针
- 全局变量:截获指针
- 成员变量:截获指针
这里就可以看到,只有当截获自动变量的基本类型数据(如int)时,block只截获其值。当然,如果你只截获到其值,并没有拿到它的内存地址,在block里面你就不能对它进行修改(如+1)。
需要特别注意的是:
strong/copy 修饰的block代码块里面截获对象的时候,会持有该对象,使其引用计数+1。
__block的本质
当我们截获了自动变量的基本类型数据,又想在block里面对它进行修改时,这个时候__block就派上用场了。
看一下基本类型数据用__block修饰之后的源码
__block int i = 0;
源码:
struct __Block_byref_i_0 {
void *__isa; //isa指针
__Block_byref_i_0 *__forwarding; //该指针指向自身
int __flags; //标记
int __size; //大小
int i; // 变量值
};
//新人只看上面这块就行了
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_i_0 *i; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_i_0 *i = __cself->i; // bound by ref
(i->__forwarding->i) ++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_3b0837_mi_0,(i->__forwarding->i));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0;
}
从源码中我们了解到,系统将带有__block的变量转换成一个block结构体。并且当block被copy到堆上的时候,一并把_block变量的结构体也copy一份到堆中。其中堆中block结构体的_forwarding指针指向其变量本身。
这样,block里面就能截获到__block变量的结构体里面的__forwarding指针,而该指针指向了堆上_block变量。这样,block就能对该变量进行修改了。
block的循环引用
首先我们来看一段代码
//block1,block2是self的成员变量,block3是person类的成员变量
//会造成循环引用
self.block1 = ^{
NSLog(@"%@",self);
};
//同样也会造成循环引用
self.block1 = ^{
NSLog(@"%@",_blcok2);
};
//同样也会造成循环引用
Person *person = [[Person alloc] init];
person.block3 = ^{
NSLog(@"%@",person);
};
让我们来看一下这个循环引用究竟是怎么产生的
看第一个,block1是self里的成员变量,那么self持有了block1,强引用,引用计数+1.然后看到block1代码里面引用了self,上面已经说了,copy/strong(默认)修饰的block里面都会持用,强引用截获的对象,这样就形成了一个闭环self->block1->self。从而导致self和block1互相引用,不会被自动释放。
再看第二个,_block2等价于self->block2,那么就形成了self->block1->self->block2,很明显,这也是一个闭环,也就是循环引用。
再看第三个,我们就能知道第三个也形成了一个闭环person->block3->person。
所以说导致循环引用的罪魁祸首并不是self,而是要看是否产生互相强引用,self本无过,只是我们在开发过程中常出现的循环引用的闭环里都包含了self。
那么如何避免循环引用呢?
既然他们是因为互相强引用导致的,那我们就把其中一个改为弱引用就好了
__weak __typeof(self) weakSelf = self; //声明一个弱引用指针
self.block1 = ^{
NSLog(@"%@",weakSelf);
};
这时,我们再来看一下他们之间的引用,self->block1->weakSelf。
由于此时,weakSelf是一个弱引用,当它再指向它的成员变量block1的时候已经不是强引用了,这时强引用循环就断开了。
当然,如果我们在block里面仅仅调用了局部变量,也就不需要给self声明弱引用指针了。
参考文章:
https://www.jianshu.com/p/ee9756f3d5f6
https://blog.csdn.net/olsQ93038o99S/article/details/83829415
https://blog.csdn.net/jingqiu880905/article/details/51997126
https://www.jianshu.com/p/53cedd7bafa4