ObjC block简析(二)

往深处看-block(一)ios

block的copy

往深处看-block(一)中咱们已经探究过在MRC环境下Block的三种类型以及其关系。api

咱们发现有些状况下ARC和MRC环境下的block是不一样的,这是由于ARC帮咱们作了一些咱们在OC层面看不见的事情。好比ARC下某些状况编译器会自动对block进行一次copy操做,将本来存在栈上的block拷贝到堆空间上。安全

block做为返回值

block做为函数返回值

上图中执行的环境是ARC,咱们能够看出本是一个__NSStackBlock__,当它做为一个函数的返回值的时候它变成__NSMallocBlock__类型,上篇中已经指出,__NSStackBlock__通过copy操做就会变成__NSMallocBlock__类型,而这里并无显式的调用copy,这即是ARC为咱们作的。iphone

block被强指针引用

block被强指针引用

block做为强指针引用的时候也会自动调用copy,将栈区的block拷贝一份到堆区。函数

其余状况

当block做为Cocoa API中方法名含有usingBlock的方法参数和block做为GCD API的方法参数时ARC都会自动为block执行一次copy操做,好比:3d

其余状况

因此,为了保证block的释放正和时宜,而不是像在栈空间上的变量似的随时可能被释放。在MRC的时候声明一个block的时候一般使用copy关键字修饰,而到了ARC虽然不用咱们显式的执行copy,为了提醒咱们block是经过copy操做存在于堆空间的咱们仍是会用copy关键修饰,这只是一个习惯,其实用strong也是能够的。指针

对象类型的捕获

往深处看-block(一)中简述了block对基本数据变量的捕获,那么对于auto对象block是怎么捕获的呢?会不会对auto对象产生相应的引用呢?code

栈上的block捕获auto对象

在MRC下声明一个属性Person,并在Block中访问其属性age,观察Person何时被释放:cdn

在栈上的block

咱们看到Person在block的大括号结束以后,尚未调用block0的时候Person对象就已经释放了,说明在栈上的block并无对Person有强引用。对象

对block0的值执行一次copy操做,让block0持有该block,而后执行上述代码,发如今block没有release以前,person对象没有被释放,因此堆上的block强引用了person对象,person执行一次release以后,person的引用计数依然没有成为0,由于,block还引用着它。这是由于当对block进行copy操做的时候,block会执行内部的__main_block_copy_0方法。__main_block_copy_0方法执行_Block_object_assign根据变量的修饰符判断对捕获的对象的引用状况(retain或者弱引用)。

而当block从堆中移除的时候,会调用与__main_block_copy_0对应的_Block_object_dispose函数,该函数会自动释放引用的auto变量。

捕获对象类型

关注上述源码中框出的部分,对比基本数据类型的变量捕获,咱们发如今Desc结构体多了上面所说的copy和dispose函数指针,它们的做用和调用时机也在上面描述了。

__weak

因为MRC状况下并无弱引用也就没有__weak这个关键字,因此在ARC下探究block对对象的引用状况。

依旧是上述代码,将他改为ARC环境:去掉block的copy,对象的release,以及Person的dealloc方法中对父类dealloc方法的调用。

而对对象的强弱引用是在runtime的时候进行断定的。因此在将.m转成.cpp的时候须要声明一下runtime环境,使用该命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

block中的person实例被__strong修饰

上面图片中 block经过_Block_object_assign判断修饰person的关键字为__strong,对person进行强引用。

而使用__weak修饰person的时候会产生弱引用:

block对person弱引用

__Block

当咱们在block内部尝试修改auto变量的时候会发现出现一个错误:Variable is not assignable (missing __block type specifier)

block内部更改auto变量

编译器提示咱们使用__block修饰auto成员变量,才能进行修改。

__block的本质

利用上面提到的命令将代码转换成.cpp查看实现,发现出现了一个新的包含有isa指针和auto变量同名成员的结构体。它的每一个成员的值以下图:

2018122115453787929122.png

当咱们修改a这个值得时候会经过结构体找到__forwarding这个指针,经过赋值咱们发现这个指针指向其自身,而后在经过__forwarding指向的结构体查找到a这个成员,改掉a这个成员的值。那么auto变量a与生成的对象a以及对象的成员a之间是什么关系呢?

咱们尝试模拟block底层的转换窥探其关系:

模拟block底层的转换

获取地址值:

打印结果

__forwarding指向的是结构体自己,而结构体内存储的a的地址跟auto变量a的地址是一致的。block内部存储的为a从新生成的对象a与auto变量自己是不一样的。

__block对象的内存管理

既然block为auto变量a生成了一个新的对象,那么这个对象在内存中是如何管理的呢?

它跟对象类型的变量同样,当block存在于栈上的时候都不会对他们产生强引用,而当block从栈上copy到堆上的时候,都会调用__main_block_copy_0,经过_Block_object_assign函数判断引用关系,而该函数的最后一个参数代表了是被__block修饰auto变量仍是对象类型的auto变量。

断定引用关系

当block从堆上移除的时候都会调用__main_block_dispose_0函数,并传入上述数字所表明的含义。

循环引用

循环引用

上述代码person引用了block,而block内部又对person进行了引用,因此此时person的引用计数为2。当person的做用域结束的时候,person的引用计数减1,而block并无被执行,因此person的引用计数依然是1,不能被回收。为了使person的内存可以正确的被回收,这个循环中必须有一个引用是弱引用才能保证对象被安全的释放。咱们一般可使用__weak或者__unsaft_unretained修饰被访问的对象。这两个关键字的含义都是声明一个不会被强引用的对象,因此block对person的引用是弱引用,因此当person的被release一次以后,就没有其余的引用了,person的引用计数就会为0,系统就会回收person的内存,完成内存释放。

使用__weak 或者 __unsaft_unretained解决循环引用

除了上述的两种方法,还能够在block使用变量以后对变量进行置nil操做。可是这中作法必须对block进行一次调用,不然,block永远都不会执行置nil的操做,block对person的引用也会一直存在。因为在block内部对person进行了修改,因此使用__block关键字对person进行修饰。而使用__block修饰以后,block又会在内部生成一个person对象,这样又会多了一次引用,与上面的状况不一样。

两种引发循环引用的状况

在block内部将person置为nil就是打破了__block引用person这条线,而后引用计数person的引用计数减1,知道person的引用计数为0的时候block释放,关于person的内存管理就完成了。

__block解决循环引用

这种作法中重要的一点是必须调用一次block,才能使block中引用person被释放。

总结

1.block是一个封装了函数调用和函数调用环境的OC对象(由于其结构体的第一个成员是isa指针)。

2.block对auto变量的捕获是值传递,static变量是指针传递,全局变量不会进行捕获。

3.block有三种类型__NSGlobalBlock__ __NSStackBlock__ __NSMallocBlock__ 访问了外部变量的block存在于栈上,通过一次copy操做会将栈上的block拷贝到堆上,即:__NSStackBlock__变为__NSMallocBlock____NSGlobalBlock__copy什么也不作,__NSMallocBlock__引用计数加1。

4.mrc下不会自动进行copy,arc环境下在block做为返回值,被强指针引用,在cocoa api中做为usingblock的参数,做为gcd api的参数的时候回自动进行一次copy操做。

5.栈上block访问了对象类型的auto变量的时候不会对其发生强引用。

6.block从栈上copy到堆上的时候,block内部会执行copy操做,_Block_object_assign函数回经过auto变量的修饰符判断发生强弱引用。

7.block从堆中移除的时候,block内部会执行dispose,将引用的对象进行释放。

8.__block修饰的值,会在block内部生成一个对象,对象中存放了__block的地址,当修改这个值得时候block内部会找到这个对象(结构体,由于其第一个成员是isa)的__forwarding(指向其自身)而后在找到这个值得地址,直接修改地址中的存放值。

9.__block生成的对象的强弱引用也是经过_Block_object_assign函数对__block产生的变量产生强引用。从堆中移除的时候调用dispose释放对__block生成的变量的引用。

10.使用__weak修饰在block中访问变量解决循环引用的问题。

相关文章
相关标签/搜索