Block
一、什么是block?
- block是封装了函数调用以及调用环境(上下文)的对象;
- 我们可以通过
clang -rewrite-objc infile.m -o outfile.cpp
命令将oc代码转成C/C++代码,可以发现block底层是一个结构体大致如下:
#import <Foundation/Foundation.h>
int main(int argc, char * argv[]) {
int a = 10;
void(^block)(void) = ^{
NSLog(@"%d",a);
};
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;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_my_mzhgbx3d20ldw1y7yx7xfs140000gn_T_main_1e0f26_mi_0,a);
}
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)};
int main(int argc, char * argv[]) {
int a = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
因此我们可以知道block底层其实是一个结构体,里面包含了isa对象指针、FuncPtr函数指针,以及我们在block内部使用到的局部变量a等。
二、block对象类型
我们通过上述2中impl.isa = &_NSConcreteStackBlock得知,该block为_NSConcreteStackBlock类型,除了_NSConcreteStackBlock block还有其他类型吗?
- _NSConcreteStackBlock栈Block,存储在栈的pop而销毁;
- _NSConcreteMallocBlock堆Block,存储在堆区需要程序员进行内存的管理;
- _NSConcreteGlobalBlock全局区Block,随着进程结束而销毁;
三、block变量捕获特性
block捕获变量可以按变量所处的环境和类型分为:
- 局部变量
- 基础数据类型变量block对其进行值拷贝(赋值操作);
- 静态类型变量block对其进行指针拷贝;
- 对象类型变量block对其值进行拷贝的同时也拷贝其内存所有权修饰符;
- 全局变量
- block不会对其值进行捕获,理由很简单因为全局变量的生命周期是跟随整个应用程序。
四、__block关键字
默认情况下block捕获的变量是不能进行赋值操作的,当我们需要对block捕获的变量进行赋值时需要对变量使用__block进行修饰。
- __block修饰符做了什么?
使用上文中提到的命令,将oc代码转写成C/C++代码可以发现,__block关键值修饰的变量被转换成一个结构体类型。
__block int a = 10;
转换成
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
所以可以得出,当我们使用__block修改变量时变量会被包装成一个对象,其值存储在对象内部当我们对变量进行赋值/取值操作时,系统会通过a.__forwarding->a来进行操作。
- __forwarding的作用是什么呢?
__forwarding保证无论是在堆还是在栈上,我们访问到的都是同一份__block变量;因为当我们对block进行copy,会生成一份堆区的__block此时系统会让栈区的__block变量的__forwarding指向堆区的_block变量,堆区的__block变量的__forwarding指向自己,如下伪代码:
heapBlock = stackBlock.copy;
heapBlock.__forwarding = heapBlock
stackBlock.__forwarding = heapBlock
- 在mrc模式下__block修饰的变量block对其进行捕获时不会对其所有权修饰符进行捕获,言外之意就是不会对其进行持有内存管理权。
五、内存管理
1. block的copy操作
| block类型 | 源 | copy结果 |
|---|---|---|
| _NSConcreteGlobalBlock | 数据段 | 什么也不做 |
| _NSConcretStackBlock | 栈区 | 生成一个堆区的block(_NSConcretStackBlock) |
| _NSConcreteGlobalBlock | 堆区 | 引用计数器+1 |
2. block的循环引用
2.1. 循环引用例子:
self.block = ^{
self
}
2.2. 循环引用的解决方法:
- 在arc模式下可以使用__weak弱引用指针解决循环引用的问题;
- 在mrc下可以使用block解决循环引用,因为block不会对包装的截获的变量进行强引用,arc下会造成内存泄漏形成引用循环但是可以手动破除。