第7章:Block的使用——一、Block介绍;二、Block的本质;三、Block截获变量;四、__block修饰符;五、Block内存管理;六、Block的循环引用

一、Block介绍

     1、Block是将函数及其执行上下文封装起来的对象。

二、Block的本质

     Block是如何将函数及其上下文封装起来的呢?下面通过源码说明。我们写一个Block的定义及调用方法,然后使用clang -rewrite-objc file.m命令查看编译之后的文件内容,来理解Block的封装、调用过程。

     Block的定义及调用方法如下:

     上面的方法经过编译后如下:

     其中,__MCBlock__method_block_impl_0是封装函数及上下文的结构体,也就是Block,__MCBlock__method_block_impl_0(…)是结构体的构造函数,__MCBlock__method_block_func_0是被封装的函数,__MCBlock__method_block_desc_0_DATA是Block的描述,multiplier是Block的入参。96667行是构造一个Block对应的结构体,并把结构体的地址赋值给Block对象。

     __MCBlock__method_block_impl_0结构体的代码如下:

     其中,__block_impl结构体的含义如下:

     __MCBlock__method_block_func_0函数的定义如下:

     看过编译之的源码后,我们知道Block的调用就是函数的调用,这在上面的_I_MCBlock_method函数中可以看出来,把函数的源码再贴出来如下:

     其中,(int (*)(__block_impl *, int))是函数FuncPtr的类型。

 

三、Block截获变量

     1、Block截获变量分如下几种情况理解

局部变量(基本数据类型、对象类型)、静态局部变量、全局变量、静态全局变量。

对于基本数据类型的局部变量截获其值;

对于对象类型的局部变量连同所有权修饰符一起截获:栈空间block不会保住auto引用对象的命,即不会对引用对象进行强引用。但是堆空间block通过强引用被访问对象,从而延长被引用对象的生命周期。所以可以对栈内存的block进行copy操作,变成堆内存上的block,这样被block引用的对象生命周期就会保住,等到block销毁的时候,被引用的对象才会销毁。参考https://www.jianshu.com/p/266df2f02838

以指针形式截获局部静态变量;

不截获全局变量、静态全局变量。

     2、源码讲解

我们写一个Block的定义及调用方法,然后使用clang -rewrite-objc -fobic-arc file.m命令查看编译之后的文件内容,来理解Block的截获过程。

Block的定义及调用方法如下:

上面的方法经过编译后如下:

__MCBlock__method_block_impl_0结构体的代码如下,看过之后我们就会明白Block对不同的变量是如何截获的:

 

四、__block修饰符

     1、什么场景下使用__block修饰符?

一般情况下,需要对被截获变量进行赋值操作时,要使用__block修饰符。

Block的内存管理。需要注意的是,赋值并不等于使用,如下只是使用,不需要__block修饰符:

下面代码是对截获变量的赋值,需要使用__block修饰符:

     2、__block修饰符的使用特点

局部变量(基本数据类型、对象类型)赋值时需要使用__block修饰符;静态局部变量、全局变量、静态全局变量赋值时不需要使用__block修饰符。全局变量、静态全局变量不需要是因为不被Block截获,静态局部变量不需要是因为传入的是其地址,直接赋值即可。基本数据类型的局部变量被截获时截获的是值,Block中赋值时需要__block修饰符修饰我可以理解;但是为何对象类型的局部变量也需要__block修饰符修饰?因为为对象指针赋值的意思是给对象指针重新指定一个实例,此时Block中对对象指针的赋值是不影响Block外部的对象指针的指向的,所以也需要__block修饰符修饰。这里我没理解对是因为我把改变对象成员的值理解成了为对象赋值的意思,我错把对象的使用理解成对象的赋值。

在Block外部__block修饰的变量编译后变成了对象,这等于是在原变量外部包了一层,Block截获时截获的是新对象的指针,通过新对象的指针就可以为原对象赋值了。

Block中对截获变量的赋值语句为multiplier = 4;,编译之后为(multiplier.__forwarding-> multiplier) = 4;

在栈中的Block对象,其__forwarding指针指向的是自己,如下图:

__forwarding是干什么的呢?Block内存管理中讲述。

 

五、Block内存管理

     上面几节源码讲述中,在__MCBlock__method_block_impl_0结构体的代码中通过isa指针为Block指定类型。Block有三种类型,_NSConcreteGlobalBlock、_NSConcreteStackBlock、_NSConcreteMallocBlock。

     Block的copy操作如下:

     栈上Block的销毁:

     copy后Block的销毁,即堆上Block的销毁:

     从上图可以看出,堆上的Block不会自动销毁。如果在MRC中将Block copy后不手动释放就会造成内存泄漏。

     栈上__block变量的Copy:

     作者认为__forwarding存在的意义为:不论在任何内存位置,都可以顺利的访问同一个__block变量。

     我的理解__forwarding的作用是使得__block变量改变的是同一个值,这样如果两个__block变量都在改变值的时候,值的改变是同步的。

 

六、Block的循环引用

     通过__weak关键字就可以避免上图的循环引用,为什么呢?__weak关键字的原理是什么?因为对于对象类型的局部变量连同所有权修饰符一起截获,在上图中截获的是strong类型的_array,如果换成__weak,则截获的是__weak类型的_array,此时就不构成循环引用。

     上图在MRC下,不会产生循环引用。在ARC下,会产生循环引用,引起内存泄漏。ARC下产生循环引用的原因如下图

     上图可以解除上面的循环引用,但是问题在于Block得不到调用的话,循环引用一直存在,所以用__weak解除循环引用比较好。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值