常用的用来分析的指令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 main.m
block本质上也是一个OC对象,它内部也有个isa指针,block是封装了函数调用以及函数调用环境的OC对象
block的内存结构
大致如下:(最简单纯粹的情况)
//不穿入任何参数的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; //block方法体
Desc = desc; //
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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的捕获机制
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
全局的变量是不会记录到block的结构体中的,局部static修饰的非oc对象会保存变量地址到block结构体中,而局部的auto类型变量,无论是否是oc对象,只会传递值,不过当时oc对象时候,穿的其实就是指针,所以调用block前对oc对象操作,会影响block内部。
1.不使用外部任何变量的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; //block方法体
Desc = desc; //
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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最简单干净的样子。
2.局部auto变量
2.1.非oc对象的auto变量
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_xp_j4ps6cyd58l9y306tt5875fxpt4kv4_T_main_56909c_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)};
会捕获进来,在block内部会生成一个属性,在创建block的时候会当时的a的值传入,所以在调用block前无论怎么改变这个a的值,都不会影响block中值。
2.1.oc对象的auto变量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MCPerson *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MCPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
MCPerson *__strong person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xp_j4ps6cyd58l9y306tt5875fxpt4kv4_T_main_07b741_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("name")));
}
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*/);}
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中使用了person的name属性,block捕获进来后是MCPerson *__strong person;
所以在block调用之前更改了,block内部的也会更改。
此时要注意,如果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)。
2.1.static修饰的auto变量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *c;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_c, int flags=0) : c(_c) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *c = __cself->c; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xp_j4ps6cyd58l9y306tt5875fxpt4kv4_T_main_36c69a_mi_1,(*c));
}
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调用之前更改变量值,会影响到block中的值。
3.全局变量
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_xp_j4ps6cyd58l9y306tt5875fxpt4kv4_T_main_8135e5_mi_1,d);
}
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中的。
block的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型,注意ARC下很多情况下都会对block自动copy。
- NSGlobalBlock ( _NSConcreteGlobalBlock ): 没有访问auto变量
- NSStackBlock ( _NSConcreteStackBlock ) : 访问了auto变量
- NSMallocBlock ( _NSConcreteMallocBlock ): __NSStackBlock__调用了copy
下面是block进行copy后的结果:
要注意在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
- block作为函数返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
__block修饰符
__block
可以用于解决block内部无法修改auto变量值的问题,__block
不能修饰全局变量、静态变量(static),编译器会将__block变量包装成一个对象。下方是它的大概样子的结构体
struct __Block_byref_x_0 {
void *__isa; //指向block的类对象
__Block_byref_a_0 *__forwarding; //指向原来的该结构体的本身
int __flags;
int __size;
val; //原来的变量值
};
- 修饰非oc对象的auto变量
__block int a = 1;
MCBlock block2 = ^(){
NSLog(@"%i",a);
};
//上方是使用,下方是大概实现
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xp_j4ps6cyd58l9y306tt5875fxpt4kv4_T_main_869314_mi_0,(a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
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};
此时a被封装成__Block_byref_a_0
结构体,在block前对a值做更改,则会影响block的值,因为你在外部的a+=3
,其实是调用(a.__forwarding->a)+=3;
,外部的那个a其实是__Block_byref_a_0
类型了,为什么要a.__forwarding而不是a.a后面会说到。
- 修饰oc对象的auto变量
struct __Block_byref_bp_0 {
void *__isa;
__Block_byref_bp_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
MCPerson *__strong bp;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_bp_0 *bp; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_bp_0 *_bp, int flags=0) : bp(_bp->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_bp_0 *bp = __cself->bp; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_xp_j4ps6cyd58l9y306tt5875fxpt4kv4_T_main_aac850_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)(bp->__forwarding->bp), sel_registerName("name")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->bp, (void*)src->bp, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->bp, 8/*BLOCK_FIELD_IS_BYREF*/);}
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};
相比非oc对象的auto变量的封装,多出来copy、dispse2个函数,会在block的copy、dispose的时候调用,调用时机上文已经说过。
__block的内存管理
要注意当block在栈上时,并不会对__block变量产生强引用
当block被copy到堆时会调用block内部的copy函数
,copy函数内部会调用_Block_object_assign
函数,_Block_object_assign
函数会对__block变量形成强引用(retain)(注意:这里仅限于ARC时会retain,MRC时不会retain)。
下方是2个block同时使用__block修饰的变量,并且都被copy的情况下的示意图:
当block从堆中移除时,会调用block内部的dispose函数
,dispose函数内部会调用_Block_object_dispose
函数,_Block_object_dispose
函数会自动释放引用的__block变量(release),下方是__block修饰的变量对2个block使用的dispose的示意图:
上文说到的为什么要a.__forwarding而不是a.a
的原因:
是因为block被copy后,__forwarding是指向复制到的__block变量结构体,所以要通过forwarding去拿到真正有效的__block变量。
循环引用问题
- 形成的原因如图
- 解决办法
- 用
__weak
解决
- 用
__block
解决(必须要调用block)
三省:
- block的原理是怎样的?本质是什么?
封装了函数调用以及调用环境的OC对象
- __block的作用是什么?有什么使用注意点?
自己感受啦
- block的属性修饰词为什么是copy?使用block有哪些使用注意?
- block一旦没有进行copy操作,就不会在堆上
- 使用注意:循环引用问题
- block在修改NSMutableArray,需不需要添加__block?
不需要,因为传入的是指针