block实现原理
block本质是一个OC对象,它内部也有个isa指针,它封装了函数调用已经函数调用环境。
首先声明一个block:
int main(int argc, const char * argv[]) {
void (^block)(void) = ^ {
NSLog(@"hello block");
};
block();
return 0;
}
然后使用clang将代码转换成C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
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;
__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_w__yq2fbvbn2xb3b55kwjxrrw0r0000gn_T_main_417806_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)};
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)
);
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
从上面的代码可以看出block的本质是一个OC对象,它的结构如下:
block的实现是一个__main_block_impl_0结构体,结构体包含两个重要的成员,一个是block对象__block_impl,另一个是block描述__main_block_desc_0
__block_impl对象中存储着block的函数实现;__main_block_desc_0记录着block占用的内存空间
block的声明和调用简化类型转换后如下:
// 首先创建一个__main_block_impl_0结构体,
// 并且将函数地址__main_block_func_0和__main_block_desc_0_DATA地址传入
void (*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA)
);
// 再调用__block_impl对象中的FuncPtr函数(实际就是上一步传入的__main_block_func_0函数)
(block)->FuncPtr)(block);
block类型捕获
局部变量捕获
int main(int argc, const char * argv[]) {
// auto 自动变量,离开作用域立刻销毁
// 定义一个局部变量a和一个静态变量b
auto int a = 10;
static int b = 10;
void (^block)(void) = ^ {
NSLog(@"a = %d", a);
NSLog(@"b = %d", b);
};
block();
return 0;
}
转换成C++代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
int *b;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), 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 a = __cself->a; // bound by copy
int *b = __cself->b; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__yq2fbvbn2xb3b55kwjxrrw0r0000gn_T_main_47e51c_mi_0, a);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__yq2fbvbn2xb3b55kwjxrrw0r0000gn_T_main_47e51c_mi_1, (*b));
}
总结:
-
auto类型的局部变量,会被捕获到
__main_block_impl_0结构体内成为成员变量,并且在__main_block_func_0函数中使用的是__main_block_impl_0结构体的成员变量 -
其中auto类型的变量是值传递
-
static类型的变量为指针传递
全局变量
int m = 10;
static int n = 10;
int main(int argc, const char * argv[]) {
void (^block)(void) = ^ {
NSLog(@"m = %d", m);
NSLog(@"n = %d", n);
};
block();
return 0;
}
转换成C++代码:
int m = 10;
static int n = 10;
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_w__yq2fbvbn2xb3b55kwjxrrw0r0000gn_T_main_859eed_mi_0, m);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__yq2fbvbn2xb3b55kwjxrrw0r0000gn_T_main_859eed_mi_1, n);
}
总结:
全局变量不会被捕获成为__main_block_impl_0的成员变量,并且__main_block_func_0函数内是直接访问全局变量
block三种类型
代码如下:
int main(int argc, const char * argv[]) {
int age = 10;
//__NSGlobalBlock__
void (^block1)(void) = ^ {
};
NSLog(@"%@", [block1 class]);
// __NSMallocBlock__
void (^block2)(void) = ^ {
NSLog(@"n = %d", age);
};
NSLog(@"%@", [block2 class]);
// __NSStackBlock__
NSLog(@"%@", [^{
NSLog(@"%d", age);
} class]);
return 0;
}
block访问外部auto变量时,会将变量捕获成为__main_block_impl_0结构体的成员变量
在ARC环境下,编译器根据一下情况会自动将栈上的block复制到堆上:
block作为函数返回值时- 将
block赋值给__strong指针时 block作为Cocoa API中方法名含有usingBlock的方法参数时
__block
-
__block可以用于解决block内部无法修改auto变量值的问题 -
__block不能修饰全局变量、静态变量 -
编译器会将
__block变量包装成一个对象int main(int argc, const char * argv[]) {
// 定义一个__block变量 __block int age = 10; void (^block)(void) = ^ { // 在block内部修改age的值 age = 20; NSLog(@"block age = %d", age); }; block(); // 在block结束之后修改age的值 age = 30; NSLog(@"age = %d", age); return 0;}
将上面代码转换成C++代码:
// 将__block变量包装成__Block_byref_age_0对象
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;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// block实现函数,内部访问的是__Block_byref_age_0包装对象的age成员变量static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__yq2fbvbn2xb3b55kwjxrrw0r0000gn_T_main_0117fb_mi_0, (age->__forwarding->age));
}
int main(int argc, const char * argv[]) {
// 创建一个__Block_byref_age_0类型的结构体,并将结构体里的age成员变量赋值为10
__Block_byref_age_0 age = {(void*)0,&age, 0, sizeof(__Block_byref_age_0), 10};
void (*block)(void) = (&__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
&age,
570425344)
);
((block)->FuncPtr)(block);
// block外部的age修改实际修改的是__Block_byref_age_0结构体内部的age成员变量
(age.__forwarding->age) = 30;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__yq2fbvbn2xb3b55kwjxrrw0r0000gn_T_main_0117fb_mi_1, (age.__forwarding->age));
return 0;
}
当__block修饰了block外部的auto变量时,
age会被包装成一个__Block_byref_age_0对象,并且被block捕获到__main_block_impl_0结构体内成为成员变量
block实现函数内部使用的正是这个__Block_byref_age_0对象的age成员变量
并且在block后面再次访问age变量时,同样等同于访问__Block_byref_age_0对象中的age成员变量,如下👇
__Block_byref_age_0 结构体访问age变量时,使用__forwarding指针的原因:
当block在栈上时,__Block_byref_age_0对象中的__forwarding指针指向__Block_byref_age_0对象本身
当block拷贝到堆上时,栈上的__Block_byref_age_0对象中的__forwarding指向堆上的__Block_byref_age_0对象
这样能保证此时栈上和堆上的__forwarding指针都指向堆上的__Block_byref_age_0对象
__block的内存管理
当block在栈上时,并不会对__block变量产生强引用
- 会调用
block内部的copy函数 copy函数内部会调用_Block_object_assign函数_Block_object_assign函数会对__block变量形成强引用
当block从堆中移除时
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的__block变量