美文网首页
Block的本质透析

Block的本质透析

作者: xxttw | 来源:发表于2023-06-05 20:20 被阅读0次

    block的底层结构

    • 底层结构
    struct __block_impl {
      void *isa; 
      int Flags;
      int Reserved;
      void *FuncPtr; // 函数地址
    };
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size; // block的内存占用大小
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    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 _a, int *_b, int flags=0) : a(_a), b(_b) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • block的内存布局


      WeChatff95bfc5e1541d5dc207875b212ab3d0.png

    Block的变量捕获

    image.png
            int a = 10;        // auto变量 值拷贝
            static int b = 10; // static修饰的局部变量 称之为静态局部变量 存储在静态存储区, 它的生命周期是整个应用程序,一直可以访问到 所以是 指针拷贝  (&b);
            void (^block)(void) = ^{
                NSLog(@"%d, %d", a, b);
            };
            a = 20;
            b = 20;
            block();
    
    // 底层会变成这样
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int 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;
      }
    };
    
    • 局部变量之所以会被捕获是因为, 可能会被跨函数访问, 所以局部变量需要被存起来, 那么存在哪里呢? 那肯定是block内部啊, 所以block会捕获它们, 它以后得用, 不然用的时候找不到了, 那不是出问题了吗
    • 全局变量, 静态全局变量不会被捕获, 因为在哪一个函数都能访问到它们

    block的类型

    image.png
    • NSGlobalBlock: 没有使用到auto变量 (static变量 全局变量也还是GlobalBlock类型)
    • NSStackBlock: 访问了auto变量 MRC下显示NSStackBlock ARC下编译器会自动对Stackblock进行copy 所以是NSMallocBlock

    NSStackBlock在栈区, 一旦出了作用域就会被销毁, block内部的数据就会是垃圾数据,为了保证安全ARC下编译器帮我们对strong强引用的对象自动调用了copy, 将其拷贝到堆区, 保证它的调用安全,所以拷贝到堆区后会显示NSMallocBlock

    • NSStackBlock一旦调用copy, 就会变成NSMallocBlock
      image.png

    捕获对象类型的auto变量

            WTBlock block = NULL;
            {
                __strong WTPerson *p = [WTPerson new];  // 当变量的所有权修饰符是 strong时, 不写默认就是strong
                __weak WTPerson *weakPerson = p;// 如果修饰符是 __weak
                p.name = @"123";
                block = ^{
                    NSLog(@"%@",p.name);
                };
            }
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      WTPerson *__strong p;          // block内部就会以strong的形式持有访问的变量
      WTPerson *__weak weakPerson;   // block内部就会以weak的形式持有访问的变量
    
    };
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    
    

    总结 堆上的block捕获auto变量 会连同对象的 所有权修饰符一起捕获, 如果捕获的是对象类型,desc结构体会新增2个函数 copydispose, 用于捕获对象的内存管理


    • block栈上, 访问了对象类型的auto变量, 是不会对auto变量产生强引用, 因为block在栈上, 它自身都随时可能被销毁, 所以没法保证访问的变量的安全, 所以不会产生强引用

    • 如果block被拷贝到堆上, 并且捕获了对象类型

    desc的结构体内会新增2个函数指针copydispose
    copy内部会调用Block_object_assign函数
    Block_object_assign函数会根据捕获的auto变量的所有权修饰符(__strong, __weak, unsafe_unretained)做出相应的内存管理操作, 形成强引用(retained)弱引用

    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
        _Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    • 如果block从堆上移除

    会调用dispose函数, dispose内部会调用Block_object_dispose 来释放引用的auto变量(类似release)

    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
      _Block_object_dispose((void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);    
    }
    

    __block的本质

    • __block会将修饰的变量包装成一个对象
    • block结构体会新增__Block_byref_age_0结构体 (ByRef 按地址传递)
      WeChat26722aced47e2c5a1e4489369c943b9d.png

    __block的内存管理

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

    • 当block被copy到堆上时, (ARC对强引用指向的对象会自动拷贝到堆上)
      1 会调用blockdesc结构体copy函数
      2 copy函数内部调用 Block_object_assign函数
      3 Block_object_assign 会对__block包装的变量形成 强引用

    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
      _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
      _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    
    
    • 一开始我们的代码是这样
            __block int age = 10;
            WTBlock block = ^{
                age = 20;
            };
            block();
    

    刚开始blockage 都是在栈上, 当block被强引用指向时, 会被编译器copy拷贝到堆上时, 也会同时将捕获的 __block变量 也拷贝到堆上, 并且对__block变量进行强引用

    image.png
    • 当block从堆中移除的时候
      1 block内部会调用dispose函数
      2 dispose函数会调用 Block_object_dispose 释放__block变量(release)
      WeChat665e4ef4cdd5bf979ab0da0d3047af32.png

    图左 block持有__block变量block被销毁时, block调用dispose函数 将__block变量进行release操作
    图右 block0 和block1同时引用__block变量, 当block0被销毁时, 对__block变量进行release操作, 它的引用计数-1, 当block1也被销毁时, 也对__block变量进行release操作, 它的引用计数为0, 最终没有持有者而被销毁

    对象类型的auto变量、__block变量

    • 当block在栈上时, 对它们不会产生强引用

    • 当block在堆上时, 会调用copy函数来处理他们
      1 __block变量 (基本数据类型a)
      _Block_object_assign(&dst->a), src->a, 8) BLOCK_FIELD_IS_BYREF
      block__block包装的对象直接就是强引用

      2 对象类型的auto变量(p)
      _Block_object_assign(&dst->p, src->p, 3) BLOCK_FIELD_IS_OBJECT
      会根据对象的所有权修饰符, 来决定是强引用还是弱引用

    • 当block从堆上移除时, 都会通过dispose函数来释放它们
      1 __block _Block_object_dispose(src->a, 8) BLOCK_FIELD_IS_BYREF
      2 auto变量 _Block_object_dispose(src->p, 3) BLOCK_FIELD_IS_OBJECT

      WeChat84cb55937b4fa3bc2997d2b4fac85a90.png

    __block 修饰对象类型的auto变量

    • 首先block的结构体还是会新增一个__Block_byref_person_0 这个和修饰基本数据类型的时候保持一致
     __block WTPerson *person = [WTPerson new];
     person.name = @"123";
    WTBlock block = ^{
       NSLog(@"%@", person.name);
    };
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_person_0 *person; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • 但是发现 __Block_byref_person_0 内发生了改变
      1 新增了copydispose函数
      2 也会有一个同类型的成员变量, 并且所有权修饰符和外面一致
    struct __Block_byref_person_0 {
      void *__isa;
    __Block_byref_person_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     WTPerson *__strong person; // 如果外面申明的时候使用的是weak 那么这里就是WTPerson *__weak person
    };
    

    block被拷贝到堆区时, block内部会调用copy函数 将__Block_byref_person_0 也拷贝到堆区,并对它产生强引用
    __Block_byref_person_0被拷贝到堆区时, __Block_byref_person_0也会调用其内部的copy函数 对WTPerson *__strong person 进行强引用或者弱引用(这个和对象的所有权修饰符有关) (只有ARC下才会强引用,MRC下一直都是弱引用)

    block被销毁时, block会调用dispose函数, 释放__Block_byref_person_0(release)
    __Block_byref_person_0 被销毁时候, 也会调用内部的dispose函数, 对WTPerson *__strong person进行一次释放操作

    __Block_byref_person_0 中的 forwarding

    • forwarding指针指向哪里?
      ARC下 forwarding不论是栈上还是堆上 var->forwarding->var 都是指向堆上的var
      MRC下forwarding 指向堆上的var

    那这个说明了forwarding是为了让我们更好的管理内存的,不论现在block是出于栈中还是堆中,都不会影响到寻找到的相关信息,当block是在栈中,__forwarding指向的就是栈本身的地址,当block copy到堆中的时候,__forwarding指针指向的就是堆本身的地址

    image.png

    Block的循环引用问题

    • __weak 不会产生强引用,指向的对象销毁时, 会将指针置为nil
    • __unsafe_unretaind 不会产生强引用, 不安全, 指向的对象销毁时, 指针存储的地址值不变
    • __block (必须调用block, 必须要清空 __block包装的对象)
    • NSProxy中间类
           __block  WTPerson *person = [WTPerson new];
            person.block = ^{
                NSLog(@"%@", person.name);
                person = nil;
            };
            person.block();
    
    image.png

    Block相关面试题

    1.block的本质是什么?

    block是封装了函数调用和函数调用环境的OC对象

    1. __block的作用是什么? 有什么使用注意点
    • __block会将修饰的对象或者基本数据类型,包装成一个对象(结构体), 对象被block强引用
    • 它可以解决block内部无法修改auto变量值的问题
    • 注意点 内存管理方面, 在MRC下 包装的对象对OC对象是不会产生强引用
    1. block的属性修饰词为什么是copy, 使用block有哪些注意点
    • block如果没有进行copy就不会再堆上, 可能是在数据区(data区)NSGlobalBlock, 也可能是在栈区NSStackBlock,它的生命周期不受我们管控,为了延长它的生命周期, 使用copy将它拷贝到堆区, 从而来管理它的生命周期, 方便我们能够安全的使用
      使用注意: 循环引用问题
    1. block内修改NSMutableArray, 需不需要加__block

    使用NSMutableArray add remove等 操作时候不需要加
    如果改变NSMutableArray指针的指向就需要加__block, 因为NSMutableArray是auto变量, 此时需要使用__block修饰符来允许对变量进行修改

    1. 使用__weak后, 为什么还需要在block内部使用__strong

    这是为了保证在block内部使用时, 用strong来强引用weak指向的弱指针, 避免在block执行时已经被释放掉了.可以保证在 Block 执行期间对象不会被提前释放。这样可以确保在 Block 内部安全地使用该对象,

    相关文章

      网友评论

          本文标题:Block的本质透析

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