美文网首页
block的本质

block的本质

作者: 知之未道 | 来源:发表于2021-08-12 10:51 被阅读0次

Block结构

block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象
block的底层结构如图所示

block的底层结构.png

声明一个block转义之后是下面这种代码

((void (*)())&__test_block_impl_0((void *)__test_block_func_0, 
                                &__test_block_desc_0_DATA, 
                                 ));
主要的方法,就是__test_block_func_0结构体的的内容 和 __test_block_desc_0_DATA结构体的描述 

block的结构

struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  int a;
  int *b;
  __test_block_impl_0(void *fp, struct __test_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;
  }
};
传入的参数会在自己的构造函数 __test_block_impl_0 中用到,这个构造函数类似于 init 方法,返回的是一个结构体
block的ISA指向的是_NSConcreteStackBlock类型,相当于,对象指向的是NSObject类型一样

block大括号里面的实现代码:

static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
  int *b = __cself->b; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_fd2a14_mi_0, a, (*b));
}

结构体的描述

static struct __test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)}; 

block的调用

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

相当于调用了block结构体重的impl对象的FuncPtr方法,但是一般是impl. FuncPtr才可以,这个却能直接调用呢,是因为将__test_block_func_0的类型直接转换成了 void (*)(__block_impl * 类型,impl 是 __test_block_func_0 结构体的第一个成员变量,所以 impl 的地址是和 __test_block_func_0 的地址一样的,所以才能转换类型成功

Block捕获器

局部变量分为auto和static,auto是默认方法,比如 int a = 10,其实全称应该是 auto int a = 10

Block捕获.png

当定义一个方法的时候,调用的时候相当于默认传递了两个参数,一个是当前对象,一个是方法名称,所以无论是在Block中使用self还是self的某个属性都会捕获当前对象

auto变量的捕获.png
- (void)test
{
    void (^block)(void) = ^{
        NSLog(@"-------%d", [self name]);
    };
    block();
}

Block类型

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )

Block的类型所在内存区域如下图:


Block的类型.png
block类型 环境
NSGlobalBlock 没有访问auto变量
NSStackBlock 访问了auto变量
NSMallocBlock NSStackBlock调用了copy
block类型 副本源的配置存储域 复制效果
NSGlobalBlock 程序的数据区域 什么也不做
NSStackBlock 从栈复制到堆
NSMallocBlock 引用计数加一

Block的copy

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况,block作为函数返回值时,将block赋值给__strong指针时,block作为Cocoa API中方法名含有usingBlock的方法参数时,block作为GCD API的方法参数时

MRC下block属性的建议写法

@property (copy, nonatomic) void (^block)(void);

ARC下block属性的建议写法

@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

当block内部访问了对象类型的auto变量时
如果block是在栈上,将不会对auto变量产生强引用

如果block被拷贝到堆上
会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

如果block从堆上移除
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量(release)

将Block从栈copy到堆中,desc中会多出两个结构体

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->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

下面这种情况下,可以看到最里面是有用到强引用的p的,这个时候编译器会全部考虑进去,最里面有p,所以当1秒之后并不会释放p对象

MJPerson *p = [[MJPerson alloc] init];
    
__weak MJPerson *weakP = p;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"1-------%@", weakP);
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"2-------%@",p);
    });
});

在Block中修改变量

方法一

设置为全局变量,或者是使用static声明变量

static age = 10;
MJBlock block2 = ^{
    age = 30;
    NSLog(@"age is %d", age);
};

方法二

使用__block修饰,__block可以用于解决block内部无法修改auto变量值的问题,__block不能修饰全局变量、静态变量(static),编译器会将__block变量包装成一个对象

__block int age = 10;
MJBlock block1 = ^{
    age = 20;
    NSLog(@"age is %d", age);
};

通过__block修饰后,会将 int age 修改为__Block_byref_age_0 *age;结构体如下,证明也是一个对象

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;//指向自己,就是 &age
 int __flags;
 int __size;
 int age;
};
__Block_byref_age_0.png

对象使用的时候并不需要加 __block,但是当修改的对象的值的时候是需要加的

NSMutableArray *array = [NSMutableArray array];
        
MJBlock block1 = ^{
    array = nil;  //报错
    [array addObject:@"123"];  //不报错
};

当我们在外部定义一个变量的时候,在block中修改,这时候这个变量就是 __block生成的结构体里面的变量地址

__block int age = 10;
        
MJBlock block = ^{
    age = 20;
    NSLog(@"age is %d", age);
};
    
struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block;
    
NSLog(@"%p", &age);  //这个地址就是__Block_byref_age_0 里面的那个变量地址

当__block变量在栈上时,不会对指向的对象产生强引用

当__block变量被copy到堆时,会调用__block变量内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用<font color="#FF0033">(注意:这里仅限于ARC时会retain,MRC时不会retain)</font>

如果__block变量从堆上移除
会调用__block变量内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放指向的对象(release)

Block内存管理

Block内存管理.png

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

当block被copy到堆时,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用(retain)

当block从堆中移除时,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的__block变量(release)

block从堆中移除.png

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

当block拷贝到堆上时,都会通过copy函数来处理它们

__block变量(假设变量名叫做a)
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

对象类型的auto变量(假设变量名叫做p)

_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

当block从堆上移除时,都会通过dispose函数来释放它们

__block变量(假设变量名叫做a)
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

对象类型的auto变量(假设变量名叫做p)

_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

__block的__forwarding指针

__forwarding.png

在block中修改值

__block int age = 10;
        
MJBlock block = ^{
    age = 20;
    NSLog(@"age is %d", age);
};

实现是这样的,先拿到 &age的地址,也就是 __Block_byref_age_0 结构体,然后拿到里面的 __forwarding指针,__forwarding指向自己,然后在拿到 __Block_byref_age_0 结构体里面的 age,为什么通过 __forwarding指针 找age是因为,使用的时候, block被强引用的时候会被copy到堆上,__forwarding的作用就是为了保证指向正确的那个地址,在堆上指向copy到堆上的地址,在栈上指向栈上的地址

__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};

(age->__forwarding->age) = 20;

Block的循环引用

导致循环引用的原因是相互持有对方,比如下面代码,person使用了强引用,然后又在block中使用了person,就导致了循环引用


Block的循环引用.png
MJPerson *person = [[MJPerson alloc] init];
        
person.age = 10;
person.block = ^{
    NSLog(@"age is %d", person.age);
};

解决方式(ARC)

用__weak、__unsafe_unretained、__block解决

__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil

__weak typeof(person) weakPerson = person;

__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变

__unsafe_unretained typeof(person) weakPerson = person;

__block的情况比较复杂,相当于是造成了一个三方的相互持有,对象->block->__block->__block内部的对象,但是我们需要手动调用下,block并且将对象置为Nil,这样就可以打破相互引用

__block MJPerson *person = [[MJPerson alloc] init];
        
person.age = 10;
person.block = ^{
    NSLog(@"age is %d", person.age);
    person = nil;
};
    
person.block();
解决方式(ARC).png

解决方式(MRC)

不支持__weak,只能使用__unsafe_unretained、__block解决

__unsafe_unretained typeof(person) weakPerson = person;

MRC的环境下__block 不会retain,所以使用方法和__unsafe_unretained一样

相关文章

  • 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/bnbfbltx.html