zoukankan      html  css  js  c++  java
  • block本质探寻一之内存结构

    一、代码——命令行模式

    //main.m

    #import <Foundation/Foundation.h>
    
    struct __block_impl {
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
    };
    
    struct __main_block_desc_0 {
        size_t reserved;
        size_t Block_size;
    };
    
    struct __main_block_impl_0 {
        struct __block_impl impl;
        struct __main_block_desc_0* Desc;
        int age;
    };
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    //        ^{
    //            NSLog(@"This is a block");
    //        }();
            
            int age = 10;
            void (^block)(int, int) = ^(int a, int b){
                NSLog(@"This is a block:%d", age);
                NSLog(@"a:%d b:%d", a, b);
            };
            
            struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;
            

             block(20, 30);

        }
        return 0;
    }

    分析:以下代码的前提,因为我们知道block底层的构造就是上述结构体的构造,桥接的目的就是展示这样的结构体内部是怎样的;

    struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;

    二、调试

    //lldb模式

    1)第一个断点

     2)第二个断点

    3)转入汇编

     

    4)汇编界面

     

    分析:

    1)我们发现内部变量的层次感:

    第一层:包含impl、Desc、age;

    第二层:impl包含isa、Flags、Reserved、FuncPtr;

    2)block大括号内部的代码的第一行的地址跟FuncPtr指针指向的地址是一样的,那么block大括号内的代码是如何存放的,跟FuncPtr指针有什么关系?往下看;

     三、将main.m文件转成底层实现代码(即C++代码)

    1)命令行:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

    说明:警告不用管; 

    2)找到main.cpp文件

    3)拖入文件并打开

    说明:不对mian.cpp文件编译的目的是,自己可以任意对该文件的代码操作而不报错; 

      

     说明:

    1)上面两张图中,1、2、3是一一对应关系(1:为block要引用的外部变量;2:定义的block;3:调用block);

    2)在2处,本人把一些强制转换去除了(如:void (*)等),便于阅读;

    四、底层代码分析

    1)block结构体(对2的分析)

    很明显,block是一个指针,指向__main_block_impl_0,那__main_block_impl_0又是什么呢,往下看;

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int age;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };

    __main_block_impl_0是个结构体,内部成员变量有__block_impl类型的结构体变量,__main_block_desc_0类型的结构体变量,外部应用变量,以及一个__main_block_impl_0方法(该方法名跟所在的结构体名称相同,为C++的一个构造方法,类似于init方法);

    <1>__block_impl

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };

    该结构体有四个成员变量,这跟上述lldb模式下显示的成员变量相同,而第一个成员变量为isa指针,我们知道这是oc对象(实例、类、元类)的专属标志,很显然__main_block_impl_0是一个oc对象,而oc对象的本质就是在内存中为结构体,此处完全吻合;

    <2>__main_block_desc_0

    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

    该结构体有两个成员变量,同时定义了一个结构体变量并对其赋值,reserved赋值为0,Block_size赋值为__main_block_impl_0即block的内存大小;

    <3>__main_block_impl_0构造函数

    __main_block_impl_0在main 函数中传了三个参数:

    //__main_block_func_0参数

    static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
      int age = __cself->age; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_51dde3_mi_0, age);
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_tb_zgsq5gq15rd3zvbdmw1c09y80000gn_T_main_51dde3_mi_1, a, b);
            }

    很显然,__main_block_func_0函数存放的是block大括号里面的代码,而该函数是直接赋值给__main_block_impl_0构造函数的第一个参数fp指针,而fp又赋值给__block_impl结构体中的FuncPtr变量,因此,回顾上述lldb模式,FuncPtr指针存放的地址跟37号断点处转入汇编模式显示的首地址是一样的,结合此处,可以肯定,FuncPtr变量指向block大括号内的代码,该代码存放在内存中的分配好的函数中(__main_block_func_0);

    //__main_block_desc_0_DATA参数

    __main_block_desc_0_DATA是一个结构体,包含了block的内存大小,最终赋值给__main_block_desc_0结构体类型变量Desc;

    //age直接赋值给_age

    构造函数中的第四个参数flags默认设置为0;

    2)block调用(对3的分析)

    block调用代码可以简化成以下代码

    (__block_impl *)block->FuncPtr(block, 20, 30);

    <1>参数:经上述分析,我们知道FuncPtr指向block大括号内的代码块即__main_block_func_0函数,该函数共有三个参数,block本身,和两个int类型变量,实参与形参一一对应,这点没问题;

    <2>强引用:因为block是一个结构体指针,其引用结构体变量只能通过"->"形式引用(如果是结构体变量非指针,则可以通过点(.)引用——此处是C语言语法知识,稍啰嗦了点!);

    但是FuncPtr并不是block(即__main_block_impl_0)结构体的成员变量,为什么能直接引用,而不应该是block->impl.FuncPtr吗?

    我们看到block进行了强制转换(__block_impl *),而__block_impl结构体中是存在FuncPtr变量的,但这完全是两个不同的结构体,也不能强制转换引用啊?

    我们可以看到,__block_impl结构体在__main_block_impl_0结构体中是第一个成员变量类型,即__main_block_impl_0的首地址其实就是__block_impl结构体的首地址,也就是说,__main_block_impl_0结构体的地址是从isa指针变量开始的,即__main_block_impl_0结构体在__block_impl结构体的大小范围内跟__block_impl结构体是完全重合的(其实就是同一片内存),只不过__main_block_impl_0结构体大小要比__block_impl结构体大——因此,经过强制转换后block完全可以直接引用FuncPtr成员变量;

    五、结论

    【1】block本质是一个oc对象,以结构体形式存放在内存中;block本身是一个指针,存放的是该结构体的内存地址(可以把block理解成函数名——函数名也是指针,指向函数的入口地址);

    【2】block大括号内的代码存放在固定的函数中,该函数的入口地址存放在block结构体的成员变量指针(FuncPtr)中;

    【3】block结构体分为函数调用结构体变量impl(包含isa指针变量、函数调用指针变量)、信息描述结构体指针变量Desc(包含block内存大小成员变量)、外部引用变量(age),以及构造函数;

    如下图所示(构造函数没有写出来,size即为block内存大小):

     

    GitHub

  • 相关阅读:
    党报
    一个人只有敢于承担责任,才有可能被赋予更大的责任。做不
    勇于担当:好男人的三块责任田——
    关于担当
    领导干部要勇于担当
    福布斯专访阿里蔡崇信:马云的坚持和改变
    阿里股权
    ContentProvider
    搞笑段子
    报业
  • 原文地址:https://www.cnblogs.com/lybSkill/p/10240705.html
Copyright © 2011-2022 走看看