Block的本质透析

2023-06-05  本文已影响0人  xxttw

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的变量捕获

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的类型

image.png
  • NSStackBlock: 访问了auto变量 MRC下显示NSStackBlock ARC下编译器会自动对Stackblock进行copy 所以是NSMallocBlock

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

捕获对象类型的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, 用于捕获对象的内存管理


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*/);
}

会调用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的内存管理

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

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


__block 修饰对象类型的auto变量

 __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;
  }
};
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是为了让我们更好的管理内存的,不论现在block是出于栈中还是堆中,都不会影响到寻找到的相关信息,当block是在栈中,__forwarding指向的就是栈本身的地址,当block copy到堆中的时候,__forwarding指针指向的就是堆本身的地址

image.png

Block的循环引用问题

       __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 内部安全地使用该对象,

上一篇下一篇

猜你喜欢

热点阅读