美文网首页
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总结

    一、Block的底层结构及本质 (1)block本质: 从代码可以看出,Block的本质就是NSObject. 也...

  • OC底层原理(八):Block

    block是经常使用的一种技术,那么block的本质是什么呢? Block的本质 block本质上也是OC对象,它...

  • 理清 Block 底层结构及其捕获行为

    来自掘金 《理清 Block 底层结构及其捕获行为》 Block 的本质 本质 Block 的本质是一个 Obje...

  • Block详解-小码哥

    block本质 block的本质是封装了函数调用和函数调用环境的OC对象。 block结构 Block_layou...

  • iOS-底层原理28:block底层原理

    本文主要介绍:1、block的本质2、block捕获变量3、block的类型4、__block原理 本质 通过cl...

  • block系列文章总结

    iOS源码解析:Block的本质<一>iOS源码解析:Block的本质<二>Objective C block背后...

  • Block详解

    block的本质 先看block的简单实现 转为C++代码 查看Block的继承关系 结论: block本质上也是...

  • 2019 知识点总结

    1、Block 释放 追问 (1)Block本质? Block本质就是一个OC对象,内部有isa指针。 Block...

  • 06-OC中block的底层原理

    06-block的本质 在讲解block的底层原理前,我们先抛出如下block相关的问题: block的本质,底层...

  • iOS Block详解

    第一部分:Block本质 Q:什么是Block,Block的本质是什么? block本质上也是一个OC对象,它内部...

网友评论

      本文标题:Block的本质透析

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