美文网首页
Block本质和类型

Block本质和类型

作者: lth123 | 来源:发表于2021-03-24 15:45 被阅读0次

    一.Block的本质


    • block在本质上也是一个oc对象 ,因为他内部有一个isa指针
    • block封装了函数调用以及函数调用的环境
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // 1.定义了一个block变量
            void(^block)(void) = ^{
                NSLog(@"Hello, World!");
            };
            
            //2. 指向block
            block();
        }
        return 0;
    }
    

    使用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 命令将main.m 转换成main.cpp,可以看到main函数的的c++实现

    // main函数
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
            void(*block)(void) = ((void (*)())&__main_block_impl_0(
                                                                   (void *)__main_block_func_0,
                                                                   &__main_block_desc_0_DATA)
                                                                  );
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    
    1.定义block的c++实现
     void(*block)(void) = ((void (*)())&__main_block_impl_0(
                                                            (void *)__main_block_func_0,
                                                             &__main_block_desc_0_DATA)
                                                            );
    

    执行了__main_block_impl_0这个函数,并且传入了(void )__main_block_func_0和&__main_block_desc_0_DATA两个参数,将__main_block_impl_0这个构造函数的返回值的地址赋值给了block指针变量,block一个指向了类型是__main_block_impl_0的指针

    我们看下__main_block_impl_0这个函数的具体实现是什么样的
    __main_block_impl_0函数实现

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc; //记录结构体大小的成员变量
        // c++ 语法,构造函数,返回结构体对象
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp; //指向__main_block_func_0,也就是block封装的代码
        Desc = desc;
      }
    };
    

    可以看到__main_block_impl_0是一个结构体,他有两个成员变量和一个构造方法

    • impl
    • Desc
    • __main_block_impl_0

    第一个成员变量impl的结构体

    // 定义了一个impl的结构体
    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr; // 指向block封装的代码块
    };
    

    在这里我们看到了isa指针,说明__block_impl的本质是一个oc对象,而__block_impl又是__main_block_impl_0的成员变量,所以__main_block_impl_0的本质也是一个oc对象。在上面我们说道,block指向了__main_block_impl_0这个结构体,所以说block本质上是指向一个oc对象的指针

    第二个成员变量Desc的结构体

    // 定义了一个描述block大小的结构体
    static struct __main_block_desc_0 {
      size_t reserved; // 0
      size_t Block_size; //__main_block_impl_0 的大小
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    

    构造方法的具体实现

     // c++ 语法,构造函数,返回结构体对象
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp; //指向__main_block_func_0,也就是block封装的代码
        Desc = desc;
      }
    

    __main_block_impl_0构造方法需要三个参数,fp赋值给了impl.FuncPtr,desc赋值给了Desc, flags 默认等于0,
    fp就是定义block时传入的(void *)__main_block_func_0
    desc就是定义block时传入的&__main_block_desc_0_DATA)

    参数1:__main_block_func_0的实现如下:

    //封装了block内部需要执行的代码
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_6a2183_mi_0);
    }
    
    static __NSConstantStringImpl __NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_6a2183_mi_0 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"Hello, World!",13};
    

    __main_block_func_0这个函数就是对应NSLog(@"Hello, World!");,__main_block_func_0函数就是block封装的代码块

    参数2:__main_block_desc_0_DATA

    // 定义了一个描述block大小的结构体
    static struct __main_block_desc_0 {
      size_t reserved; // 0
      size_t Block_size; //__main_block_impl_0 的大小
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    

    __main_block_desc_0_DATA主要是记录 __main_block_impl_0这个结构体的大小

    定义block过程总结:

    1.首先执行__main_block_impl_0这个构造方法,传入__main_block_func_0和__main_block_desc_0_DATA两个参数。参数1记录了block代码的具体实现,参数2记录了__main_block_impl_0这个结构体的size;

    2. __main_block_impl_0将__main_block_func_0赋值给impl.FuncPtr,将__main_block_impl_0赋值给Desc;

    3.__main_block_impl_0初始化成功后,会返回一个__main_block_impl_0类型的结构体,并且将返回值的地址复制给block。

    2.如何执行block
     ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    

    block变量就是指向__main_block_impl_0的地址,通过block变量找到__main_block_impl_0成员变量里面的FuncPtr,在定义block的时候,将block封装的代码块的地址赋值给了FuncPtr,只需要执行函数FuncPtr().

    ((__block_impl )block) 强制类型转换,强制将block的类型转换成__block_impl,所以才能反问到FuncPtr

    上面的block是最简单的block,没有参数,没有返回值,也没有访问外部的变量

    二.Block传入参数

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            void(^block)(int, int) = ^(int a,int b){
                NSLog(@"%d--%d",a,b);
            };
            
            block(5,10);
        }
        return 0;
    }
    
    // 对应的c++实现
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            void(*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    
    
            ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 5, 10);
        }
        return 0;
    }
    
    1.((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 5, 10);可以看到在执行block的时候传入了5和10,
    
    2.封装block具体实现也多了两个参数,a和b
    static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_9045fc_mi_0,a,b);
    }
    

    二.Block访问自动变量(没用static修饰的局部变量,离开作用域会销毁)

    自动变量会被block捕获,访问方式是值传递;修改block外面的值不会改变block捕获的值

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int a = 10;
            void(^block)(void) = ^(){
                NSLog(@"%d",a);
            };
            a = 20;
            block();
        }
        return 0;
    }
    输出结果是10
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            int a = 10;
            // 函数是值传递,在定义block的时候传入的是10
            void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
            // 修改自动变量a的值为20,不会影响block内部的成员变量a
            a = 20;
            // 打印成员变量a的值10
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    
    1.在执行__main_block_impl_0函数的时候多传了一个参数a,此时a的值是10;
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int a; // 保存捕获的值10
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    2.__main_block_impl_0结构体里面多了一个成员变量a,用来保存在初始化传入的10;
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      // 获取block成员变量a的值10
      int a = __cself->a; // bound by copy
      NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_707356_mi_0,a);
    }
    3.在执行block的时候,获取的是__main_block_impl_0内部的成员变量a,而不是外面的自动变量a,所以打印的是10
    

    三.Block访问static修饰的局部变量

    局部变量会被block捕获,访问方式是地址传递;修改block外面的值会改变block捕获的值

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int a = 10;
            static int b = 10;
             block = ^(){
                NSLog(@"%d--%d",a,b);
            };
            a = 20;
            b = 20;
        }
        return 0;
    }
    

    输出结果: 10,20

    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
            // 自动变量a
            int a = 10;
            // static 修改是局部变量b
            static int b = 10;
            
            // 执行__main_block_impl_0,传入 a的值和 b的地址
            void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, &b));
            a = 20;
            b = 20;
            // 执行block
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int a; //保存a的值
      int *b; // 保存b的地址
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      // 获取a的值10
      int a = __cself->a; // bound by copy
      // 获取b的地址存储的内容 20
      int *b = __cself->b; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_ed5e58_mi_0,a,(*b));
    }
    
    

    - 捕获外面的局部变量时,分别捕获的是a的值10,b的地址&b;
    - 将捕获的10和&b分别保存到__main_block_impl_0结构体的a和b中;
    - 执行block时,取出a的值10和指针b指向的地址所存储的内容20;

    为什么自动变量是值传递,而静态局部变量是地址传递?

    static的作用:

    static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
    根本原因是自动变量出了作用域会被释放,而静态局部变量不会被释放,而block的执行时机是不确定的,如果自动变量也是地址传递,在执行block的时候,自动变量有可能被释放,当时传入的地址是不可用的,会出现坏的内存访问。

    void(^block)(void);
    void testBlock(){
        int a = 10;
        static int b = 10;
         block = ^(){
            NSLog(@"%d--%d",a,b);
        };
        a = 20;
        b = 20;
    
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            testBlock();
            block();
        }
        return 0;
    }
    
    

    testBlock()执行完成后,自动变量a的已经被释放了,而b不会被释放;在执行block()的时候,如果访问a的地址,会出现坏内存访问


    四.Block访问全局变量 static修饰的全局变量

    Block访问全局变量和 static修饰的全局变量,不会捕获

    
    int a = 10;
    static int b = 10;
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
     NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_46ed42_mi_0,a,b);
    }
    
    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)};
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
            a = 20;
            b = 20;
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    

    可以看到a和b定义在全局区,__main_block_impl_0里面并没有成员变量去保存a和b
    在执行__main_block_func_0函数的时候,是直接访问a和b。

    为什么访问局部变量会捕获,访问全局变量不会捕获

    作用域问题:
    局部变量只能在函数内部访问,在别的函数中是无法访问另外一个函数定义的局部变量的。
    block的实现是在__main_block_func_0函数中,变量定义在其他的函数中,在__main_block_func_0中是无法访问他的函数中定的变量的,所以需要捕获;而全局变量,在任何地方都能访问,所以不需要捕获就能访问


    image.png

    五.Block的类型

    block有三种类型,可以通过class方法或者 isa看具体类型,最终都集成自NSBlock

    1. NSGlobalBlock
    2. NSStackBlock
    3. NSMallocBlock

    三种类型的block的内存分配情况

    image.png image.png

    栈上的block是系统自动分配内存,自动回收内存,堆上的block由开发者申请内存,释放内存
    如果是栈上的block捕获了局部变量,在其他地方调用时,会出现意向不到的结果

    void (^block)(void);
    
    void testBlock(){
        int a = 10;
        block = ^{
            NSLog(@"a的值是:%d",a);
        };
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            testBlock();
            block();
        }
        return 0;
    }
    

    a的值是随机的,并不是10,这是因为 ^{NSLog(@"a的值是:%d",a);} 在栈上,当testBlock函数执行完毕后,内存会被回收,全局变量block指向的内存区域的数据不再是当时的数据.

    void (^block)(void);
    
    void testBlock(){
        int a = 10;
        block = [^{
            NSLog(@"a的值是:%d",a);
        } copy];
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            testBlock();
            block();
        }
        return 0;
    }
    
    a的值是10
    

    NSStackBlock执行copy操作后,会将栈上的block拷贝到堆上, 变成NSMallockBlock,在堆上,内存由开发者释放,这个时候block全局变量指向堆上的block,block不会被释放

    在ARC环境下,以下情况,栈block会自动调用copy,变成堆block

    1.栈 block被强指针引用时会copy到堆中

    //MRC
    typedef void(^MyBlock)(void);
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
        int a = 2;
        MyBlock block = ^{
            NSLog(@"%d",a);
        };
        block();
        NSLog(@"%@",[block class]);
        }
        return 0;
    }
    

    在MRC环境下,block的类型是NSStackBlock,而在ARC下,block的类型是NSMallocBlock,因为有block强指针引用;

    typedef void(^MyBlock)(void);
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int  a = 1;
            NSLog(@"%@",[^{
                NSLog(@"%d",a);
            } class]);
        }
        return 0;
    }
    

    访问了局部变量,没有强指针引用,在MRC和ARC下,block都是NSStackBlock类型

    2.栈 block作为函数的返回值会被copy到堆上

    // arc环境,访问了局部变量,栈block
    typedef void(^MyBlock)(void);
    void testBlock(){
        int a = 10;
        NSLog(@"%@",[^{
            NSLog(@"%d",a);
        } class]);
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
          testBlock();
        }
        return 0;
    }
    
    //  arc环境,访问了局部变量,但是作为了函数的返回值,堆block
    typedef void(^MyBlock)(void);
    MyBlock testBlock(){
        int a = 10;
        return ^{
            NSLog(@"%d",a);
        };
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
          NSLog(@"%@",[testBlock() class]);
        }
        return 0;
    }
    

    3.cocoa API中的block和GCD中的block,会进行copy操作,在堆中


    六.Block访问auto类型的oc对象

    • 如果是栈block,不会对auto变量产生强引用。
      因为栈block的内存是由系统回收的,出了作用域就会被回收,不会在作用域之外执行block,而auto变量也是出了作用域被回收,强引用oc对象的目的就是为了在执行block的时候使用的变量是正确的,栈block和auto对象的生命周期相同,所以没有必要强引用auto类型的oc对象
    • 如果block被拷贝到堆上: 1.会调用block内部的copy函数;2.copy函数会调用_Block_object_assigin函数; 3. _Block_object_assigin会根据 auto变量内存管理修饰符(__strong, __weak ,__unsafe_unretained)来做出对应的操作(强引用/弱引用)

    如果block从堆上移除

    • 调用内部的dispose函数
    • dispose函数会调用内部的_Blokc_object_dispose函数.
    • _Blokc_object_dispose会根据内存管理修饰符解除对auto变量的引用

    1.用strong修饰

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      Book *book; // strong指针
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Book *_book, int flags=0) : book(_book) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
        _Block_object_assign((void*)&dst->book, (void*)src->book, 3/*BLOCK_FIELD_IS_OBJECT*/);
        
    }
    
    

    2.用__weak修饰

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      Book *__weak weakBook; // weak 指针
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Book *__weak _weakBook, int flags=0) : weakBook(_weakBook) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
        _Block_object_assign((void*)&dst->weakBook, (void*)src->weakBook, 3/*BLOCK_FIELD_IS_OBJECT*/);
        
    }
        
    }
    
    

    相关文章

      网友评论

          本文标题:Block本质和类型

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