美文网首页
iOS block底层详解

iOS block底层详解

作者: 恋空K | 来源:发表于2019-08-22 11:35 被阅读0次

说到外部变量,我们要先说一下C语言中变量有哪几种。一般可以分为一下5种:

自动变量

函数参数

静态变量

静态全局变量

全局变量

研究Block的捕获外部变量就要除去函数参数这一项,下面一一根据这4种变量类型的捕获情况进行分析。

我们先根据这4种类型

自动变量 (就是局部变量)

静态变量

静态全局变量

全局变量

写出Block测试代码。

这里很快就出现了一个错误,提示说自动变量没有加__block,由于__block有点复杂,我们先实验静态变量,静态全局变量,全局变量这3类。测试代码如下:

#import

int global_i = 1;

 static int static_global_j = 2 ;

int main (intargc,constchar*argv[]) {

static int static_k = 3;

int val = 4;

void(^myBlock)(void)=^{

global_i++;

static_global_j++;

static_k++;

NSLog(@"Block中 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);

};

global_i++;

static_global_j++;

static_k++;

val++;

NSLog(@"Block外 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);

myBlock();

return0; }

运行结果

Block 外  global_i=2,static_global_j=3,static_k=4,val=5

Block 中  global_i=3,static_global_j=4,static_k=5,val=4

这里就有2点需要弄清楚了

1.为什么在Block里面不加__bolck不允许更改变量?

2.为什么自动变量的值没有增加,而其他几个变量的值是增加的?自动变量是什么状态下被block捕获进去的?

首先全局变量global_i和静态全局变量static_global_j的值增加,以及它们被Block捕获进去,这一点很好理解,因为是全局的,作用域很广,所以Block捕获了它们进去之后,在Block里面进行++操作,Block结束之后,它们的值依旧可以得以保存下来。

在__main_block_impl_0中,可以看到静态变量static_k和自动变量val,被Block从外面捕获进来,成为__main_block_impl_0这个结构体的成员变量了。

*_static_k=4;val=4;

到此,__main_block_impl_0结构体就是这样把自动变量捕获进来的。也就是说,在执行Block语法的时候,Block语法表达式所使用的自动变量的值是被保存进了Block的结构体实例中,也就是Block自身中。

这里值得说明的一点是,如果Block外面还有很多自动变量,静态变量,等等,这些变量在Block里面并不会被使用到。那么这些变量并不会被Block捕获进来,也就是说并不会在构造函数里面传入它们的值。

Block捕获外部变量仅仅只捕获Block闭包里面会用到的值,其他用不到的值,它并不会去捕获。

小结一下:

到此为止,上面提出的第二个问题就解开答案了。自动变量是以值传递方式传递到Block的构造函数里面去的。Block只捕获Block中会用到的变量。由于只捕获了自动变量的值,并非内存地址,所以Block内部不能改变自动变量的值。Block捕获的外表变量可以改变值的是静态变量,静态全局变量,全局变量。

回到上面的例子上面来,4种变量里面只有静态变量,静态全局变量,全局变量这3种是可以在Block里面被改变值的。仔细观看源码,我们能看出这3个变量可以改变值的原因。

静态全局变量,全局变量由于作用域的原因,于是可以直接在Block里面被改变。他们也都存储在全局区。

静态变量传递给Block是内存地址值,所以能在Block里面直接改变值。

根据官方文档我们可以了解到,苹果要求我们在自动变量前加入 __block关键字(__block storage-class-specifier存储域类说明符),就可以在Block里面改变外部自动变量的值了。

总结一下在Block中改变变量值有2种方式,一是传递内存地址指针到Block中,二是改变存储区方式(__block)。

先来实验一下第一种方式,传递内存地址到Block中,改变变量的值。

Block 外 str=Hello,

Block 中 str=Hello,World!

看结果是成功改变了变量的值了,转换一下源码。

在__main_block_func_0里面可以看到传递的是指针。所以成功改变了变量的值。

1.从捕获外部变量的角度上来看

_NSConcreteStackBlock:

只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。

StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了。

_NSConcreteMallocBlock:

有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制

_NSConcreteGlobalBlock:

没有用到外界变量或只用到全局变量、静态变量的block为_NSConcreteGlobalBlock,生命周期从创建到应用程序结束。

没有用到外部变量肯定是_NSConcreteGlobalBlock,这点很好理解。不过只用到全局变量、静态变量的block也是_NSConcreteGlobalBlock

输出:

<__NSGlobalBlock__:0x100001050> 

Block中 变量=2 3 1

可见,只用到全局变量、静态变量的block也可以是_NSConcreteGlobalBlock。

所以在ARC环境下,3种类型都可以捕获外部变量。

2.从持有对象的角度上来看:

_NSConcreteStackBlock是不持有对象的。

_NSConcreteMallocBlock是持有对象的。

_NSConcreteGlobalBlock也不持有对象

由第二章里面详细分析的,堆上的Block会持有对象。我们把Block通过copy到了堆上,堆上也会重新复制一份Block,并且该Block也会继续持有该__block。当Block释放的时候,__block没有被任何对象引用,也会被释放销毁。

__forwarding指针这里的作用就是针对堆的Block,把原来__forwarding指针指向自己,换成指向_NSConcreteMallocBlock上复制之后的__block自己。然后堆上的变量的__forwarding再指向自己。这样不管__block怎么复制到堆上,还是在栈上,都可以通过(i->__forwarding->i)来访问到变量值。

Block在捕获住__block变量之后,并不会复制到堆上,所以地址也一直都在栈上。这与ARC环境下的不一样。

ARC环境下,一旦Block赋值就会触发copy,__block就会copy到堆上,Block也是__NSMallocBlock。ARC环境下也是存在__NSStackBlock的时候,这种情况下,__block就在栈上。

MRC环境下,只有copy,__block才会被复制到堆上,否则,__block一直都在栈上,block也只是__NSStackBlock,这个时候__forwarding指针就只指向自己了。

根据打印出来的结果来看,ARC环境下,Block捕获外部对象变量,是都会copy一份的,地址都不同。

在ARC环境下,不仅仅是声明了__block的外部对象,没有加__block的对象,在block内部也会被retain。因为加了__block,只是对一个自动变量有影响,它们是指针, 相当于延长了指针变量的声明周期,只要访问对象的话还是会retain。)

对于非对象的变量来说,

自动变量的值,被copy进了Block,不带__block的自动变量只能在里面被访问,并不能改变值。

带__block的自动变量 和 静态变量 就是直接地址访问。所以在Block里面可以直接改变变量的值。

而剩下的静态全局变量,全局变量,函数参数,也是可以在直接在Block中改变变量值的,但是他们并没有变成Block结构体__main_block_impl_0的成员变量,因为他们的作用域大,所以可以直接更改他们的值。

值得注意的是,静态全局变量,全局变量,函数参数他们并不会被Block持有,也就是说不会增加retainCount值。

相关文章

网友评论

      本文标题:iOS block底层详解

      本文链接:https://www.haomeiwen.com/subject/nwngsctx.html