block详解

block本质:

每个OC对象内部都有isa指针,block也不例外;block其实就是封装函数调用,以及函数调用环境的OC对象。

block底层结构:

block底层结构

block的类型:

内存几大区域

常见的有以下三种block:

NSMallocBlock :存放在堆区的 Block

NSStackBlock  : 存放在栈区的 Block

NSGlobalBlock : 存放在全局区的 Block

当block 没有截获外部变量、截获全局变量的都是属于全局区的 Block,即 GlobalBlock;其余的都是栈区的 Block,即 StackBlock;

对于全局区的 Block,是不存在作用域的问题,但是栈区 Block 不同,在作用域结束后就会 pop 出栈,__block 变量也是在栈区的,同理作用域结束也会 pop 出栈。

为了解决作用域的问题,block 提供了 copy 函数,将 block 从栈复制到堆上,在 MRC 环境下需要我们自己调用 block_copy  函数,这里就是为什么 MRC 下,我们为什么需要用 copy 来修饰 Block 的原因。

MRC情况下:

MRC的情况下

ARC情况下:

编译器会根据情况自动添加copy,将栈上的block拷贝到堆上,比如以下情况:

1、block作为函数返回值

2、block赋值给__strong指针

3、block作为Cocoa API中的方法名含有usingBlock的方法参数时

4、block作为GCD API参数时

每一种类型的block调用copy后的结果如下:

使用copy结果示意图

block捕获机制:

block捕获机制
捕获auto、static局部变量

因为auto的局部变量在离开作用域会销毁,所以block内部使用值传递的方式捕获到,因此在block内部不能更改auto变量的值,像int因为值是直接存储在变量内存里面,所以不能更改;而static因为是指针传递所有能更改。

更改mArr内容
更改mArr指针地址

因为NSMutableArray 变量里面其实存的是指针地址,所以直接操作指针里面的内容是可以的,如果直接更改变量存储的指针地址就不行。

如果是全局变量不会捕获,不管在哪个函数都能直接访问;局部变量因为作用域的问题,可能存在跨函数访问所以会捕获。

如果block中引用实例变量(比如说name)不管是通过self.name还是_name,block都会捕获self对象.因为方法的调用默认传递两个参数(self,sel)所以self为局部变量,block引用局部变量都会捕获。

既然block对局部auto变量是值传递,那么如果想要在block内部修改auto变量怎么修改?

如果block外部变量是auto变量可以使用__block;如果是static静态变量 或者全局变量,则不需要修饰也能更改。(参考捕获机制)

原理:在ARC环境下,编译器会尽可能给我们自动添加 copy 操作。使用copy从栈复制到堆上,__block 修饰的变量也会从栈复制到堆上;为了结构体 __block 变量无论在栈上还是在堆上,都可以正确的访问变量,我们需要 forwarding 指针forwarding指针的作用:保证当我们将 Block 从栈拷贝到堆中,修改的变量都是同一份。

forwarding原理:在 Block 从栈复制到堆上的时候,原本栈上结构体的 forwarding 指针,会改变指向,直接指向堆上的结构体。这样子就可以保证之后我们都是访问同一个结构体中的变量,这里就是为什么 __block 修饰的变量,在 Block 内部中可以修改的原因了。

forwarding指针

__block底层实现:

当__block变量在栈上时:

并不会对指向的对象产生强引用。

当__block变量被copy到堆上时:

copy函数内会调用_Block_object_assign函数;_Block_object_assign函数会根据auto变量(对象类型)的修饰符(__strong,__weak, __unsafe_unretained)做出相应的操作形成强、弱引用;

当__block变量被从堆上移除时:

会调用Block内部的dispose函数 dispose函数内部会调用_Block_object_dispose函数 _Block_object_dispose函数会自动释放引用的auto变量,类似于release。

block引起循环引用场景:

当 A 持有 B,B 又持有 A,这个时候就会出现循环引用。Block 对于外部变量都会追加到结构体中,所以在实现 Block 时候需要注意这个问题。

解决方案:

ARC 环境一般我们用 __weak 来打破,MRC 环境的话,我们可以使用 __block 来打破循环引用。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 目录1.Block 的基本使用2.Block 的底层数据结构3.Block 的变量捕获机制3.1 auto 类型的...
    师大小海腾阅读 1,021评论 0 9
  • iOS开发---Block详解 Block的基础 什么是Blocks? 用一句话来描述:带有自动变量的匿名函数(是...
    祀梦_阅读 1,012评论 0 6
  • 第一部分:Block本质 Q:什么是Block,Block的本质是什么? block本质上也是一个OC对象,它内部...
    sheldon_龙阅读 567评论 0 0
  • 1.明白如何定义block类型 定义Block类型: typedef 返回值类型 Block名字 参数 block...
    哈库呐玛塔塔__阅读 670评论 0 0
  • 一、Block本质 Block是“带有自动变量值的匿名函数”。 所谓的匿名函数就是不带有名称的函数 但它究竟是什么...
    枫叶情结阅读 512评论 1 0