zoukankan      html  css  js  c++  java
  • iOS底层原理探索Block本质(三)

    首先,我们看两段代码:

    在这里插入图片描述

    从运行结果可以看出,如果是普通局部变量age,第17行和第22行的age地址是一样的,第20行的地址跟前面两个是不同的。
    这个原因我们在上节已经分析过。是因为:
    第17和第22行的age是age的地址
    第20行的age是捕获进去的age,其是一个在block内部新建的同名age,因此,地址不同。

    在这里插入图片描述

    转化为底层代码可以看到:

    在这里插入图片描述

    从底层代码可以看出,三者最后都是取的&(age.__forwarding->age),也就是被包装为__Block_byref_age_0类型后里面的age。可以解释2—和3—地址一样。

    按说1—的地址应该也是一样的,不知为何1—的地址不同

    上面两端代码都是在ARC环境下进行的,是不是ARC又为我们做了哪些操作,从而造成这样的结果呢?
    我们分别将两段代码运行在MRC上

    在这里插入图片描述

    这个结果跟在ARC上的结果是一致的,不再谈论

    在这里插入图片描述

    这个结果就跟我们跟在ARC上的结果是不同的,同样,我们看下其底层源码:

    在这里插入图片描述

    源码也一样,怎么结果就是不一样呢?到底ARC为我们做了什么?

    我们继续做实验

    在这里插入图片描述

    在ARC下,我们添加了几个打印,可以看出在ARC下,blcok的类型是NSMallocBlock,存储在堆上的。

    修改为MRC后:

    在这里插入图片描述

    不一样的地方出现了,在MRC下block的类型是NSStackBlock类型的,存储在栈上。

    按照我们之前的结论:
    访问auto变量的block是NSStackBlock类型的。
    将^{}block赋值给强指针YZBlockL类型的block,也就是 ^{}block有强指针引用,因此,在ARC下 ^{}block 会自动将栈上的block复制到堆上,所以,block的类型是NSMallocBlock。

    也就三个打印的age都是&(age.__forwarding->age)的地址。
    但是1—是在栈上的age。2—和3—都是访问的堆上面的age。

    上面的代码,也可以看出,使用__block修饰的age变量,底层的时候,也调用了copy和dispose相关函数,并在copy函数里面通过__Block_object_assign使用&dst->age,访问age,并对age进行强引用操作。而&dst就是__main_block_impl_0,其里面的age就是__Block_byref_age_0 *age。
    换句话说,就是使用_block修饰的变量,底层会进行copy操作,并在copy操作中对里面转换为__Block_byref_age_0类型的age进行相应的内存管理。

    __block的内存管理

    当block在栈上时,并不会对__block变量产生强引用

    当block被copy到堆上时
    会调用block内部的copy函数
    copy函数内部会调用__Block_object_assign函数

    __Block_object_assign函数会对__block修饰的变量进行内存管理
    在这里插入图片描述

    这个图表明,在进行copy操作后,block会从栈复制到堆上面。同时,会将block内部访问的使用__block修饰的变量也复制到堆上面,并且,堆上面的block还是会对堆上面的__block修饰的变量进行引用关系。

    如果,有两个block,同时引用一个__block修饰的变量呢?
    在这里插入图片描述

    两个block同时引用一个__block修饰的变量,会对__block进行相当于+2的操作

    在这里插入图片描述

    运行结果

    2020-04-28 16:55:09.859862+0800 block学习[5641:192209] 1----0x7ffeefbff4f8
    2020-04-28 16:55:09.860258+0800 block学习[5641:192209] 2----0x100703b48
    2020-04-28 16:55:09.860365+0800 block学习[5641:192209] ---30---
    2020-04-28 16:55:09.860406+0800 block学习[5641:192209] 3----0x100703b48
    2020-04-28 16:55:09.860437+0800 block学习[5641:192209] ---30---
    2020-04-28 16:55:09.860465+0800 block学习[5641:192209] 4----0x100703b48
    

    可以看到2、3、4的age都是同一个内存地址。

    在这里插入图片描述

    在这里插入图片描述

    上面两个图可以看到,一个是__main_block_impl_0类型,一个是__main_block_impe_1类型,但是从定义可以看出

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_age_0 *age; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    struct __main_block_impl_1 {
      struct __block_impl impl;
      struct __main_block_desc_1* Desc;
      __Block_byref_age_0 *age; // by ref
      __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    里面的age统一都是__Block_byref_age_0类型,因此,上图里面&dst->age访问的age都是同一个,是__Block_byref_age_0类型里面的age。

    当block从堆中移除的时
    会调用block内部的dispose函数
    dispose函数内部会调用__Block_object_dispose函数

    __Block_object_dispose函数会自动释放引用的__block修饰的变量
    在这里插入图片描述

    在这里插入图片描述

    __block的__forwarding指针

    在__block修饰变量的几次例子中,我们都看到,最后访问里面的age是使用age.__forwarding->age访问的方式,这是为什么呢?
    首先,我们知道__forwarding指向的就是他自己,那么, 为什么不直接访问里面的age变量,而是通过__forwarding指针访问age变量呢?

    这是因为,如果我们访问的age变量在栈上的时候,通过__forwarding找到它自己,然后访问里面的age,很容易找到age,这是行的通的,虽然复杂了一步。
    但是,如果block已经复制到堆上面,__forwarding的作用就突显出来了。__forwarding指针会指向堆上面的block,这样里面访问的__block修饰的变量,确保了是堆上面的。

    注:复制并不是剪切,栈上面是还有的。
    在这里插入图片描述

    前面我们讲的是__block修饰基础变量类型,如果用__block修饰对象类型的变量,将会是怎样的情形呢?

    __block修饰对象类型

    在这里插入图片描述

    代码通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-12.0.0 main.m指令,转换为底层代码
    部分代码为:

    在这里插入图片描述

    可以看出,使用__block修饰的对象类型,也会生成一个__Block_byref_person_0类型且变量名为person的变量。在__Block_byref_person_0定义里面,有一个YZPerson类型且变量名为person的变量。
    首先,我们看到这个person是__strong修饰的。
    那么,如果使用__weak修饰对象的话,会是怎样的情况呢?
    在这里插入图片描述

    首先,我们要明确一点,__weak是修饰person对象的,而跟__block无关。这也就解释了为何__block __weak int age = 10;会有一个警告。因为__weak只能修饰对象。
    转换为底层代码可以看到:

    在这里插入图片描述

    可以看到,__weak是修饰的YZPerson类型的person对象,而跟__Block_byref_weakPerson_0类型无关。
    也就是__block修饰的对象生成的__Block_byref_weakPerson_0类型,都是强引用。而__Block_byref_weakPerson_0里面的YZPerson类型的person对象是强还是弱,跟外部使用的强或弱有关。

    在这里插入图片描述

    继续研究发现

    在这里插入图片描述

    __Block_byref_weakPerson_0内部也有一个__Block_byref_id_object_copy和__Block_byref_id_object_dispose函数。这是使用__block修饰基本数据类型所没有的两个新东西。
    那么,这两个新东西是什么呢?

    我们知道,当block被复制到堆上时,__block修饰的变量也会被拷贝到堆上面去,当__block修饰的变量被拷贝到堆上时,内部就会调用上图两个函数,对其进行内存管理相关操作。

    在对__block修饰的变量进行转换的时候,我们可以看到:

    __block __weak YZPerson *weakPerson = person;
    转换为:
    
    __attribute__((__blocks__(byref))) __attribute__((objc_ownership(weak))) __Block_byref_weakPerson_0 weakPerson = {
    (void*)0,
    (__Block_byref_weakPerson_0 *)&weakPerson, 
    33554432, 
    sizeof(__Block_byref_weakPerson_0), 
    __Block_byref_id_object_copy_131, 
    __Block_byref_id_object_dispose_131, 
    person
    };
    
    对应
    struct __Block_byref_weakPerson_0 {
      void *__isa;
    __Block_byref_weakPerson_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     YZPerson *__weak weakPerson;
    };
    

    可以看到__Block_byref_id_object_copy_131传给了 void (* __Block_byref_id_object_copy)(void, void);
    __Block_byref_id_object_dispose_131, 传给了 void (* __Block_byref_id_object_dispose)(void*);

    通过定义可以看到:
    在这里插入图片描述

    可以看到,其内部也有一个__Block_object_assign或__Block_object_dispose函数。并且里面有一个dst+40的操作,dst+40后是谁呢?

    在这里插入图片描述

    可以看出,dst+40正好指向的是YZPerson类型的weakPerson对象。这样就看出,__Block_byref_id_object_copy和__Block_byref_id_object_dispose是对YZPerson类型的weakPerson对象进行内存管理的。

    当__block变量在栈上时,不会对指向的对象产生强引用
    
    当__block变量被copy到堆时
    会调用__block变量内部的copy函数
    copy函数内部会调用__Block_object_assign函数
    __Block_object_assign函数会根据所指向对象的修饰符(__strong\__weak\__unsafe_unretain)做出相应的操作,形成强引用或弱引用(注意:ARC才会强或弱,MRC只会是弱)
    
    如果__block变量从堆上移除
    会调用__block变量内部的dispose函数
    dispose函数内部会调用__Block_object_dispose函数
    __Block_object_dispose函数会自动释放指向的对象
    

    关于上面注意里面的话,我们可以举一个在MRC下的例子:

    在这里插入图片描述

    在23行,block还没释放,person就已经被释放了,这说明:
    在MRC下,__block后面修饰的变量是强或者弱都没有关系,最后block对__block修饰的变量都是弱引用


    block的循环引用问题

    首先,看一段代码

    在这里插入图片描述

    可以看到,在19行结束后,也没有打印person消失的信息,说明person没有消失,被循环引用了。

    img

    大致就是,block内部其实是有一个强指针引用了YZPerson类型的person对象,指向着YZPerson。而YZPerson对象里面有一个_block的成员变量,指向着对应的block,这就产生了循环引用问题。

    左上角的block是指的16行等号右边的block
    右边的YZPerson(MJPerson)是指的14行等号右边的东西
    左下角的person是指的14行左边的内容

    在ARC下解决循环引用一般使用__weak或者__unsafe_unretained
    两者的区别是:
    __weak会在指向的对象消失的时候,将指针置为nil;

    __unsafe_unretained会在指向的对象消失的时候,不会将指针置为nil;

    当然,也可以借助__block来解决循环引用问题
    在这里插入图片描述

    如果没有person = nil的情况下,其各种关系是如下:

    在这里插入图片描述

    __block指的是:__block修饰的person
    对象指的是:(__block修饰的person)里面的YZPerson类型的person对象
    Block指的是:person.block(),是YZPerson类型的person对象拥有的成员变量
    Block持有__block,是指的16行代码,等号右边的内容

    加上一句置为nil的操作后,其关系为:
    在这里插入图片描述

    在MRC下,可以使用__unsafe_unretained解决循环引用。因为MRC没有__weak,也就不存在使用__weak解决循环引用的问题了。
    使用__unsafe_unretained可以解决的原因是,block内部引用对象的时候,不会对引用计数器+1。
    另外,我们也可以直接使用__block解决循环引用问题。原因的话,我们在之前有讲过,就是因为,在MRC下,block对__block修饰的变量都是相当于弱引用,不会对引用计数器+1。

  • 相关阅读:
    POJ 1953 World Cup Noise
    POJ 1995 Raising Modulo Numbers (快速幂取余)
    poj 1256 Anagram
    POJ 1218 THE DRUNK JAILER
    POJ 1316 Self Numbers
    POJ 1663 Number Steps
    POJ 1664 放苹果
    如何查看DIV被设置什么CSS样式
    独行DIV自适应宽度布局CSS实例与扩大应用范围
    python 从入门到精通教程一:[1]Hello,world!
  • 原文地址:https://www.cnblogs.com/r360/p/15788194.html
Copyright © 2011-2022 走看看