前言
本文属笔记性质,是对kirito_song和冰凌天两位文章学习和转载
文章具体地址:
MJiOS底层笔记--Block
小码哥iOS学习笔记第八天: block的底层结构
小码哥iOS学习笔记第九天: block的类型
小码哥iOS学习笔记第十天: __block和block内存管理
什么事Block
- Block是将函数及其执行上下文封装起来的对象
{
int age = 20;
void (^block)(int, int) = ^(int a , int b){
NSLog(@"this is a block! -- %d", age);
};
block(10, 10);
}
源码解析
- 使用【clang -rewrite-objc xxx.m】查看编译后的文件内容
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc; //block描述信息(大小等)
int age; //封装了函数调用所需环境(内部定义了一个age变量)
//c++的构造函数 age(_age)表示_age将会自动赋值给age
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
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, int b) {
int age = __cself->age; // 将block当初捕获的变量,赋值给执行函数
//函数调用
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_0, age);
}
struct __block_impl {
void *isa; //表明block也属于OC对象
int Flags;
int Reserved;
void *FuncPtr;
};
//其中函数调用会被单独封装成__main_block_func_0方法。在block定义时,传入block结构体。
void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
//而在block结构体中,会被赋值给impl.FuncPtr = fp;,将函数地址存储在block内部。
//最终,在调用block时,获取FuncPtr,传入参数执行调用。
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
block的本质
封装了函数调用以及调用环境的OC对象
- block本质上也是OC对象,内部也存在isa指针
- block内部封装了函数调用,以及函数调用所需的环境(参数)
Block类型
-
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型- NSGlobalBlock ( _NSConcreteGlobalBlock )
- NSStackBlock ( _NSConcreteStackBlock )
- NSMallocBlock ( _NSConcreteMallocBlock )
-
这三种类型在内存中分别存储在不同的区域
- __NSGlobalBlock__存在于内存的数据区域(.data区)
- __NSStackBlock__存在于内存的栈区
- __NSMallocBlock__存在于内存的堆区

在MRC下
-
block来源(auto即局部变量)
- 内部没有使用
auto类型变量的block, 就是__NSGlobalBlock__类型 - 内部使用了
auto类型变量的block, 就是__NSStackBlock__类型 __NSStackBlock__类型的block调用copy后就是__NSMallocBlock__类型, 通过copy, 将block从栈区复制到了堆区
- 内部没有使用
-
对于不同类型的block,调用copy会有不同操作
__NSGlobalBlock__类型的block调用copy后类型不变,还是__NSGlobalBlock__类型(还在数据区)__NSMallocBlock__类型的block调用copy后类型不变,还是__NSMallocBlock__类型(不会生成新的block, 原有引用计数+1)
在ARC下
-
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
block作为函数返回值时
MJBlock myblock() { return ^{ NSLog(@"---------"); }; }- 将
block赋值给__strong指针
MJBlock block = ^{ NSLog(@"---------%d", age); };block作为Cocoa API中方法名含有usingBlock的方法参数时
[@[] enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { }];block作为GCD API的方法参数时
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ });__NSGlobalBlock__类型的block, 不管怎样类型都不会改变, 依然在数据区
截获变量
变量捕获
- 为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
- c语言中的局部变量,默认都为auto变量。所以auto代指局部变量

局部变量
1. 基本数据类型
由于局部变量的生命超出作用域就会被销毁。为保证
block能够正常执行,局部变量在被block捕获时,会将值传递给block的构造函数。(源码同上方源码解析)
2. 对象类型(ARC环境下)
- 在
block捕获对象类型的局部变量时。__main_block_desc_0结构体内部会多出__main_block_copy_0和__main_block_dispose_0函数,在block移动到堆空间时堆对象进行适当的retain和release。- 对于对象而言,传递的都是指针。
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [NSObject new]; //OC对象
void (^block)(void) = ^{
NSLog(@"obj is %@", obj); //捕获
};
obj = nil;
block();
}
return 0;
}
<!--c++源码-->
//block结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//如果外部为__weak内部也会为__weak
NSObject * __strong obj; //对于局部变量,结构体中保存变量与原本相同。
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSObject *obj = __cself->obj; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_tz_hcmmb5t57v1cr81ydm6s5s140000gn_T_main_d73acf_mi_0, obj);
}
//当block从栈移动到堆中时,执行此方法。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
//会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
//当block从堆中移除(释放)时,执行此方法。
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
//release引用的变量
_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
//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};
// main函数
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
//在构造block时,将OC对象传入
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
3.静态局部变量
由于静态变量的生命常驻于内存,但使用仅限于作用域内部。所以静态变量在被
block捕获时,只要将指向变量值的指针(地址)传递给构造函数,即能保证block正常执行。
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int b = 10;
void (^block)(void) = ^{
NSLog(@"height is %d", b); //height is 20
};
b = 20;
block();
}
return 0;
}
<!--c++源码-->
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *b; //b为指针类型
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_b, int flags=0) : b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *b = __cself->b; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_fd2a14_mi_0, (*b));
}
// main中构建block.将b的指针传递给构造函数
void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &b));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
全局变量和静态全局变量
由于全局变量的生命常驻于内存,并且使用不受作用域限制。所以全局变量并不需要被捕获,在执行
block的函数调用时,直接使用全局变量,即能保证block正常执行。
int age_ = 10;
static int height_ = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^{
NSLog(@"age_ is %d ,height_ is %d", age_,height_);
};
age_ = 20;
height_ = 20;
block();
}
return 0;
}
<!--c++源码-->
int age_ = 10;
static int height_ = 10;
// block结构体内部,也并未声明对应的变量
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 flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 执行函数中,直接使用全局变量
NSLog((NSString *)&__NSConstantStringImpl__var_folders_tz_hcmmb5t57v1cr81ydm6s5s140000gn_T_main_6f323a_mi_0, age_,height_);
}
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, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); //在执行block构造函数时,并未将全局变量传递进去
age_ = 20;
height_ = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
__block修饰符
__block可以解决block内部无法修改局部变量值的问题__block不能修饰全局变量和静态变量(static), 只能修饰auto变量- 编译器会将
__block变量包装成一个对象(__Block_byref_age_0),被声明的值作为对象的属性存在。
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
MJBlock block = ^{
__strong int myage = age;
age = 20;
NSLog(@"age is %d", age);
};
block();
}
return 0;
}
//__block对象结构体
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
//block结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *p;
//不再是int age;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_p, __Block_byref_age_0 *_age, int flags=0) : p(_p), age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// block执行函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
//从__Block_byref_age_0结构体中获得age变量,并且修改
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_e2457b_mi_0);
}
//c++ main函数
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//__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};
//等价于
__Block_byref_age_0 age = {0,
&age,
0,
sizeof(__Block_byref_age_0),
10};
//构建block结构体
MJBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, (__Block_byref_age_0 *)&age, 570425344));
//等价于
MJBlock block = &__main_block_impl_0(__main_block_func_0,
&__main_block_desc_0_DATA,
p,
&age,
570425344);
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
__forwarding指针
__forwarding指向__block所包装的对象实体,以确保使用时的正确性。
-
当
block在栈中,__forwarding所指实体为栈上的__block包装对象。
-
当
block移动到堆上,__block包装对象在堆中也会被复制一份。而二者的__forwarding指针都指向堆中的__block包装对象。
__block修饰OC对象
OC对象的强弱引用不会体现在block结构体中(都是strong),而是体现在__Block_byref结构体中。
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *person = [[MJPerson alloc] init];
__block __weak JSPerson *weakPerson = person;
MJBlock block = ^{
NSLog(@"%p", weakPerson);
};
block();
}
return 0;
}
struct __Block_byref_weakPerson_0 {
void *__isa; // 8
__Block_byref_weakPerson_0 *__forwarding; // 8
int __flags; // 4
int __size; // 4
void (*__Block_byref_id_object_copy)(void*, void*); // 当block被移动到堆,会对__Block_byref对象进行return(需要注意MRCblock进入堆中时不会retain该变量)
void (*__Block_byref_id_object_dispose)(void*); // 当block从堆中移除,会对__Block_byref对象进行release
MJPerson *__weak weakPerson; //__block包装的结构体中实际为weak引用。
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_weakPerson_0 *weakPerson; // // 依旧是strong引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakPerson_0 *_weakPerson, int flags=0) : weakPerson(_weakPerson->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};