block底层原理学习
一、block的本质
int a = 10;
void (^Block)(void) = ^{
NSLog(@"%d",a);
};
使用clang转换OC为C++代码
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_b4_xvb__z9916b4jgm4xkg60n_00000gn_T_main_0adf31_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, const char * argv[]) {
int a = 10;
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
return 0;
}
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
通过c++代码可见block内部有个isa指针,本质上也是一个oc对象。
- block是封装了函数调用以及函数调用环境的OC对象
二、block的变量捕获
2.1、值传递
int main(int argc, const char * argv[]) {
int a = 10;
void (^Block)(void) = ^{
NSLog(@"%d",a);
};
return 0;
}
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;
}
};
int main(int argc, const char * argv[]) {
int a = 10;
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
return 0;
}
block在创建时,传入了变量a的值,并将a的值赋给了main_block_impl_0的成员a,整个过程是值传递。
- 当block内部访问了外部的局部变量(auto
类型)时,block可以捕获到该局部变量,并以值传递的方式传递给block内部。
2.1、指针传递
int main(int argc, const char * argv[]) {
static int a = 10;
void (^Block)(void) = ^{
NSLog(@"%d",a);
};
return 0;
}
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;
}
};
int main(int argc, const char * argv[]) {
static int a = 10;
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
return 0;
}
block在创建时,传入了变量a的地址,并将a的地址赋给了main_block_impl_0的成员*a,整个过程是指针传递。
- 当block内部访问了外部的局部变量(static
类型)时,block可以捕获到该局部变量,并以指针传递的方式传递给block内部。
2.3、全局变量
static int a = 10;
int b = 21;
int main(int argc, const char * argv[]) {
void (^Block)(void) = ^{
NSLog(@"%d",a);
NSLog(@"%d",b);
};
return 0;
}
static int a = 10;
int b = 21;
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_b4_xvb__z9916b4jgm4xkg60n_00000gn_T_main_263450_mi_0,a);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_b4_xvb__z9916b4jgm4xkg60n_00000gn_T_main_263450_mi_1,b);
}
int main(int argc, const char * argv[]) {
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
return 0;
}
当a,b作为全局变量时,block就不会捕获到内部,直接访问就可以了。
截屏2020-05-26 上午11.36.58.png
2.3、测试题
static int a = 10;
int b = 21;
int main(int argc, const char * argv[]) {
static c = 22;
int d = 20;
void (^Block)(void) = ^{
NSLog(@"%d",a);
NSLog(@"%d",b);
NSLog(@"%d",c);
NSLog(@"%d",d);
};
a = 5;
b = 5;
c = 5;
d = 5;
Block();
return 0;
}
只有c的值还是22,不会被修改,因为block内部捕获到的变量是值传递。
三、block的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型:
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )
3.1、MRC
nt main(int argc, const char * argv[]) {
int a = 10;
void (^block1)(void) = ^{
NSLog(@"%d",a);
};
static int b = 10;
void (^block2)(void) = ^{
NSLog(@"%d",b);
};
void (^block3)(void) = ^{
};
void (^block4)(void) = ^{
NSLog(@"%d",c);
};
void (^block5)(void) = ^{
NSLog(@"%d",d);
};
NSLog(@"%@",[block1 class]); __NSStackBlock__
NSLog(@"%@",[block2 class]);__NSGlobalBlock__
NSLog(@"%@",[block3 class]);__NSGlobalBlock__
NSLog(@"%@",[block4 class]);__NSGlobalBlock__
NSLog(@"%@",[block5 class]);__NSGlobalBlock__
return 0;
}
- 访问了auto变量,block的类型是_NSStackBlock
- 没有访问auto变量,block的类型一般是NSGlobalBlock
int a = 10;
void (^block)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"%@",[block class]); __NSStackBlock__
NSLog(@"%@",[[block copy] class]); __NSStackBlock__
- NSStackBlock调用了copy后的类型是NSMallocBlock
每一种类型的block调用copy后的结果如下所示:
image.png image.png
3.2、ARC
static int b = 10;
void (^block2)(void) = ^{
NSLog(@"%d",b);
};
void (^block3)(void) = ^{
};
void (^block4)(void) = ^{
NSLog(@"%d",c);
};
void (^block5)(void) = ^{
NSLog(@"%d",d);
};
NSLog(@"%@",[block2 class]); __NSGlobalBlock__
NSLog(@"%@",[block3 class]);__NSGlobalBlock__
NSLog(@"%@",[block4 class]);__NSGlobalBlock__
NSLog(@"%@",[block5 class]);__NSGlobalBlock__
- 没有访问auto变量,block的类型是NSGlobalBlock
int a = 10;
1、强指针
void (^block1)(void) = ^{
NSLog(@"%d",a);
};
2、弱指针
__weak void (^block2)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"%@",[block1 class]);__NSMallocBlock__
NSLog(@"%@",[block2 class]); __NSStackBlock__
3、没有指针
NSLog(@"%@", [^{
NSLog(@"%d",a);
}class]);__NSStackBlock__
- 在ARC环境下,block被强指针指向时,编译器会根据情况自动将栈上的block复制到堆上。
- 访问了auto变量,block的类型是_NSStackBlock
@property (copy, nonatomic) void (^testBlock)(void);
@property (weak, nonatomic) void (^test1Block)(void);
@property (strong, nonatomic) void (^test2Block)(void);
int a = 10;
self.testBlock = ^(){
NSLog(@"%d",a);
};
self.test1Block = ^(){
NSLog(@"%d",a);
};
self.test2Block = ^(){
NSLog(@"%d",a);
};
NSLog(@"%@",[self.testBlock class]);__NSMallocBlock__
NSLog(@"%@",[self.test1Block class]);__NSStackBlock__
NSLog(@"%@",[self.test2Block class]);__NSMallocBlock__
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.test1Block();
}
self.test1Block的类型是_NSStackBlock,存储在栈区。过了作用域就会销毁,这样再次调用就会访问了坏内存,造成崩溃。这个例子也说明了MRC下block属性为什么一般用copy,就是 为了内存的访问安全考虑。
3.3、小结
-
1、没有访问auto变量,block的类型是NSGlobalBlock
-
2、 访问了auto变量,block的类型是_NSStackBlock
-
3、 在ARC环境下,block被强指针指向时,编译器会根据情况自动将栈上的block复制到堆上:
a、block作为函数返回值时
b、将block赋值给__strong指针时
c、block作为Cocoa API中方法名含有usingBlock的方法参数时
d 、block作为GCD API的方法参数时 -
4、
MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
3.4、对象类型的auto变量
当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)
4、__block修饰符
__block可以用于解决block内部无法修改auto变量值的问题
_block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象
__block int a = 10;
void (^block1)(void) = ^{
a = 0;
};
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
(a->__forwarding->a) = 0;
}
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};
int main(int argc, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
return 0;
}
本质就是 __block变量被编译器 包装成 __Block_byref_a_0 对象,__main_block_func_0接收的是一个对象,再通过修改对象的指针修改age的值。