Block详解

2020-03-30  本文已影响0人  CoderKK
窥探block底层结构

我们写下一个最简单的block使用clang指令生成对应的C\C++代码

void (^block)(void) = ^{
    NSLog(@"Hello, World!");
};
block();

截取关键代码如下

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  // 构造函数(类似于OC的init方法),返回结构体对象
  __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;
  }
};
// 封装了block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            //这一句就是打印"Hello, World!"
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c60393_mi_0);
}

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

// 定义block变量
void (*block)(void) = &__main_block_impl_0(
                                           __main_block_func_0,
                                           &__main_block_desc_0_DATA
                                           );

// 执行block内部的代码
block->FuncPtr(block);

从上面代码可以看出,block本质上也是一个OC对象,内部也有个isa指针,并且内部封装了函数调用。

block的变量捕获

写下一个访问外部变量的block

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

生成C\C++代码,截取关键部分

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
};
//block内部的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2w_5f0_0k152c5d825xmdf9ztmr0000gn_T_main_b9df52_mi_0,age);
}

int age = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

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

我们可以观察到block结构体多了个age变量,并且在初始化block时,将外部的age变量赋值给了结构体内部这个age变量,当函数执行时,直接打印的是结构体内部的age变量。
所以我们可以总结一下,block就是封装了函数调用以及函数调用环境的OC对象
为了保证block内部能正常访问外部的变量,block有个变量捕获机制

变量类型 是否捕获 访问方式
auto局部变量 值传递
static局部变量 指针传递
全局变量 直接访问

局部变量不写修饰默认就是auto变量,其实从内存上也很好理解block的捕获机制。auto局部变量在栈区,函数调用完后资源会被释放掉,而static局部变量是在程序运行过程中一直存在的(存在数据段),所以用指针随时可以找到,而全局变量本来就是在哪都可访问,根本没必要捕获。

block的类型

block有三种类型,可以通过class方法或者isa指针查看具体的类型,它们最终都继承自NSBlock

类型 判断依据 存储区域 调用copy结果
__NSGlobalBlock__ 没有访问auto变量 数据段 什么也不做
__NSStackBlock__ 访问了auto变量 栈区 从栈区复制到堆
__NSMallockBlock__ __NSStackBlock__调用了copy 堆区 引用计数增加

在ARC环境下会根据情况自动将栈上的block复制到堆上,比如以下情况

block访问对象类型的auto变量

写下如下代码,用clang指令生成C\C++

NSObject *obj = [[NSObject alloc] init];
void (^block)(void) = ^{
    NSLog(@"%@",obj);
};
block();

截取部分关键代码,可以看到
当block访问对象类型的auto变量时,内部多了copy函数和dispose函数

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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代码
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
__block修饰符

__block可以用于解决block内部无法修改auto变量问题,__block不能用来修饰全局变量,静态变量(static)

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

生成C\C++代码

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
}
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};
//__block int age = 10;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344))();
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2w_5f0_0k152c5d825xmdf9ztmr0000gn_T_main_ad80c7_mi_0,(age.__forwarding->age));

当我们使用__block修饰age变量时,会将age变量包转成age对象,age对象里的int age存储着最初的age值,__forwarding指针是指向age对象自己,block捕获的是age对象的地址值。从最后的一句可以看出,当我们使用__block修饰auto变量后,访问age都变成了访问age对象里的age成员变量。

block循环引用问题

从上面我们可以看到,block访问对象类型的auto变量时有可能会产生强引用,当访问的auto变量又对block产生强引用时就会发生循环应用。举例如下

typedef void(^MyBlock)(void);
@interface Person : NSObject
@property (nonatomic, copy) MyBlock myBlock;
@end
//main函数里面
Person *person = [[Person alloc] init];
person.myBlock = ^{
    NSLog(@"%@",person);
};

在ARC环境下可以使用__weak、__unsafe_unretained解决(一般使用__weak,会自动置nil)

Person *person = [[Person alloc] init];
//或者 __unsafe_unretained typeof(Person *) weakPerson = person;
__weak typeof(Person *) weakPerson = person;
person.myBlock = ^{
    NSLog(@"%@",weakPerson);
};

在MRC环境下可以使用__unsafe_unretained解决

上一篇下一篇

猜你喜欢

热点阅读