美文网首页
Objective-C的本质(6)——Block本质

Objective-C的本质(6)——Block本质

作者: yangfei02821 | 来源:发表于2020-04-28 18:19 被阅读0次

    参考:
    iOS-Block本质
    iOS底层原理总结 - 探寻block的本质(一)
    iOS底层原理总结 - 探寻block的本质(二)

    1、Block的底层结构:

    结构体里面有一个impl结构体,impl里面有一个FuncPtr指针,里边放着将来调用函数的地址
    还有一个Desc结构体,用来描述block,里边放着block有多大,如果auto变量或者__block变量是对象类型就会多copy和dispose来做内存管理。
    还有一个age,里面要用到外部的局部变量
    还有一个构造函数,能够生成自身结构体对象,要传的两个参数为函数地址和描述信息。

    2、Block本质指向了一个结构体对象

    定义完block,首先将代码块,封装到func_0的函数里面
    紧接着会为block生成一个impl_0结构体,里边有两个成员变量impl和Desc,
    然后在调用结构体的构造函数,传了两个参数,一个函数地址,一个是描述,都会赋值给结构体的两个成员变量中,创造出一个结构体对象
    然后将结构体对象地址赋值给block变量,
    最终block变量指向了内存中的的impl_0结构体,
    当调用bolck就是找到impl,在找到FuncPtr,然后调用函数

    总的来说block是一个OC对象,也有个isa,代表block是三种类型的哪一个,
    里边封装了函数的地址,block有多大,外部要使用的一些局部变量。
    封装了函数的调用和函数的调用环境(参数之类)。

    3、block捕获的外部变量和block调用的参数分别添加在那个函数中:

    如果封装的函数(代码块)需要参数,会在那个函数中增加参数,调用block的时候传递参数。
    如果内部需要外部的变量,在构造函数中增加参数,会自动赋值给结构体中的成员变量

    4、__block本质:

    一旦使用__block,就将这个变量包装成一个结构体__Block_byref_age_0,里边也有isa,还有一个forwarding,指向自己,还有一个是age,这个age就是当初外边的那个变量,整个结构体被生成的block结构体拥有着。
    最终生成block结构体中有一个指向__block生成的结构体,这个结构体里面有另外一个成员,存储着当初的auto变量,
    一旦在block代码块中修改值,通过block对象的指针找到结构体,然后找到结构体里面成员,将这个成员改掉。

    5、__forwarding作用:

    将block从栈上复制到堆上的时候,__block也会复制到堆上,这个时候栈上的__forwarding从在栈上指向自己,变成指向堆上的自己,这样,当给变量赋值,不管__block在栈上还是在堆上,都会正确的给堆上的结构体内的变量赋值。
    疑问点:什么时候拿到的会是栈上的啊,只是一个容错处理吗???

    6、block结构体保存的是一个指针的时候。

    牵扯到指针,我就能通过指针找到那块内存,拿到那块内存中的数据进行修改。
    Block内部有个指针,指针指向对象类型的地址,然后通过指针找到对象类型的内存,将对象类型内的int age改掉。

    7、block的内存管理

    • 如果是修饰对象类型的auto 变量或者__block变量,涉及到了内存管理,desc生成的会在多加两个方法,copy和dispose函数,对对象的引用计数做操作
    • 如果__block修饰的对象类型,__block结构体也会多copy和dispose函数,对生成的__block结构体内的对象类型进行操作。
    • 注意点:Mrc下,就算拷贝到堆上,__block不会对对象强引用

    8、__block修饰的变量,打印的地址是哪里;

    是保存在新生成的__block结构体中变量的地址。

    9、copy的原因

    在MRC环境下
    一旦访问了auto变量,block就会放到栈上,为避免作用域结束,栈上block释放,使用copy操作,将栈上block拷贝到堆上block,保存block其中对象类型的auto变量的值
    \color{red}{不管MRC还是ARC都会有: 栈空间上的block,不会持有对象;堆空间的block,会持有对象。}

    10、为什么类的属性可以直接在block中赋值

    • 因为block捕获的不是属性,而是捕获的self,使用self指针对其内存空间的属性进行更改。

    • Self调用函数的时候传递进来的参数,是一个局部变量会被捕获。
      _成员变量,类里面的一个成员变量,相当于self->成员变量,捕获的还是self变量
      实例对象是auto类型,原来什么类型,现在就是什么类型。
      类型* 是一个整体,默认是一个强指针,指向实例对象

    • 捕获实例对象,相当于值传递,可以在block中更改实例对象其中的属性值,但是不能更改实例对象。
      这是因为你只是使用了这个指针,没有修改指针
      如果在外部更改属性能拿到最新的值。
      地址传递可以直接更改地址内部的值,比如,int类型10->20
      值传递基本类型不能直接更改,实例对象可以使用内存地址,不能更改

    11、局部变量和全局变量

    值传递:自动销毁的变量,内存随时可能会销毁。所以在当初创建block对象的时候,将age的值捕获进来了,提前保存,避免作用域结束,变量就消失。不能在外部更改值。
    地址传递:全局局部变量,永远在内存中,可以通过指针访问到最新的值
    捕获:block内部专门新增一个成员来存储外面的值,因为作用域的问题,跨函数访问,当定义block的那个函数调用完,局部变量不能在调用block的另一个个函数直接访问,所以将局部变量捕获到block中。
    全局变量:
    不用捕获,直接访问,只要定义好,在哪个作用域都可以使用

    相关文章

      网友评论

          本文标题:Objective-C的本质(6)——Block本质

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