一.Block的本质
- block在本质上也是一个oc对象 ,因为他内部有一个isa指针
- block封装了函数调用以及函数调用的环境
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1.定义了一个block变量
void(^block)(void) = ^{
NSLog(@"Hello, World!");
};
//2. 指向block
block();
}
return 0;
}
使用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 命令将main.m 转换成main.cpp,可以看到main函数的的c++实现
// main函数
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)
);
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
1.定义block的c++实现
void(*block)(void) = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA)
);
执行了__main_block_impl_0这个函数,并且传入了(void )__main_block_func_0和&__main_block_desc_0_DATA两个参数,将__main_block_impl_0这个构造函数的返回值的地址赋值给了block指针变量,block一个指向了类型是__main_block_impl_0的指针
我们看下__main_block_impl_0这个函数的具体实现是什么样的
__main_block_impl_0函数实现
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc; //记录结构体大小的成员变量
// c++ 语法,构造函数,返回结构体对象
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp; //指向__main_block_func_0,也就是block封装的代码
Desc = desc;
}
};
可以看到__main_block_impl_0是一个结构体,他有两个成员变量和一个构造方法
- impl
- Desc
- __main_block_impl_0
第一个成员变量impl的结构体
// 定义了一个impl的结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; // 指向block封装的代码块
};
在这里我们看到了isa指针,说明__block_impl的本质是一个oc对象,而__block_impl又是__main_block_impl_0的成员变量,所以__main_block_impl_0的本质也是一个oc对象。在上面我们说道,block指向了__main_block_impl_0这个结构体,所以说block本质上是指向一个oc对象的指针
第二个成员变量Desc的结构体
// 定义了一个描述block大小的结构体
static struct __main_block_desc_0 {
size_t reserved; // 0
size_t Block_size; //__main_block_impl_0 的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
构造方法的具体实现
// c++ 语法,构造函数,返回结构体对象
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp; //指向__main_block_func_0,也就是block封装的代码
Desc = desc;
}
__main_block_impl_0构造方法需要三个参数,fp赋值给了impl.FuncPtr,desc赋值给了Desc, flags 默认等于0,
fp就是定义block时传入的(void *)__main_block_func_0
desc就是定义block时传入的&__main_block_desc_0_DATA)
参数1:__main_block_func_0的实现如下:
//封装了block内部需要执行的代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_6a2183_mi_0);
}
static __NSConstantStringImpl __NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_6a2183_mi_0 __attribute__ ((section ("__DATA, __cfstring"))) = {__CFConstantStringClassReference,0x000007c8,"Hello, World!",13};
__main_block_func_0这个函数就是对应NSLog(@"Hello, World!");,__main_block_func_0函数就是block封装的代码块
参数2:__main_block_desc_0_DATA
// 定义了一个描述block大小的结构体
static struct __main_block_desc_0 {
size_t reserved; // 0
size_t Block_size; //__main_block_impl_0 的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
__main_block_desc_0_DATA主要是记录 __main_block_impl_0这个结构体的大小
定义block过程总结:
1.首先执行__main_block_impl_0这个构造方法,传入__main_block_func_0和__main_block_desc_0_DATA两个参数。参数1记录了block代码的具体实现,参数2记录了__main_block_impl_0这个结构体的size;
2. __main_block_impl_0将__main_block_func_0赋值给impl.FuncPtr,将__main_block_impl_0赋值给Desc;
3.__main_block_impl_0初始化成功后,会返回一个__main_block_impl_0类型的结构体,并且将返回值的地址复制给block。
2.如何执行block
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
block变量就是指向__main_block_impl_0的地址,通过block变量找到__main_block_impl_0成员变量里面的FuncPtr,在定义block的时候,将block封装的代码块的地址赋值给了FuncPtr,只需要执行函数FuncPtr().
((__block_impl )block) 强制类型转换,强制将block的类型转换成__block_impl,所以才能反问到FuncPtr
上面的block是最简单的block,没有参数,没有返回值,也没有访问外部的变量
二.Block传入参数
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(int, int) = ^(int a,int b){
NSLog(@"%d--%d",a,b);
};
block(5,10);
}
return 0;
}
// 对应的c++实现
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void(*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 5, 10);
}
return 0;
}
1.((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 5, 10);可以看到在执行block的时候传入了5和10,
2.封装block具体实现也多了两个参数,a和b
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_9045fc_mi_0,a,b);
}
二.Block访问自动变量(没用static修饰的局部变量,离开作用域会销毁)
自动变量会被block捕获,访问方式是值传递;修改block外面的值不会改变block捕获的值
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
void(^block)(void) = ^(){
NSLog(@"%d",a);
};
a = 20;
block();
}
return 0;
}
输出结果是10
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10;
// 函数是值传递,在定义block的时候传入的是10
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
// 修改自动变量a的值为20,不会影响block内部的成员变量a
a = 20;
// 打印成员变量a的值10
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
1.在执行__main_block_impl_0函数的时候多传了一个参数a,此时a的值是10;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; // 保存捕获的值10
__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;
}
};
2.__main_block_impl_0结构体里面多了一个成员变量a,用来保存在初始化传入的10;
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 获取block成员变量a的值10
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_707356_mi_0,a);
}
3.在执行block的时候,获取的是__main_block_impl_0内部的成员变量a,而不是外面的自动变量a,所以打印的是10
三.Block访问static修饰的局部变量
局部变量会被block捕获,访问方式是地址传递;修改block外面的值会改变block捕获的值
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
static int b = 10;
block = ^(){
NSLog(@"%d--%d",a,b);
};
a = 20;
b = 20;
}
return 0;
}
输出结果: 10,20
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 自动变量a
int a = 10;
// static 修改是局部变量b
static int b = 10;
// 执行__main_block_impl_0,传入 a的值和 b的地址
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, &b));
a = 20;
b = 20;
// 执行block
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; //保存a的值
int *b; // 保存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) {
// 获取a的值10
int a = __cself->a; // bound by copy
// 获取b的地址存储的内容 20
int *b = __cself->b; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_ed5e58_mi_0,a,(*b));
}
- 捕获外面的局部变量时,分别捕获的是a的值10,b的地址&b;
- 将捕获的10和&b分别保存到__main_block_impl_0结构体的a和b中;
- 执行block时,取出a的值10和指针b指向的地址所存储的内容20;
为什么自动变量是值传递,而静态局部变量是地址传递?
static的作用:
static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
根本原因是自动变量出了作用域会被释放,而静态局部变量不会被释放,而block的执行时机是不确定的,如果自动变量也是地址传递,在执行block的时候,自动变量有可能被释放,当时传入的地址是不可用的,会出现坏的内存访问。
void(^block)(void);
void testBlock(){
int a = 10;
static int b = 10;
block = ^(){
NSLog(@"%d--%d",a,b);
};
a = 20;
b = 20;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
testBlock();
block();
}
return 0;
}
testBlock()执行完成后,自动变量a的已经被释放了,而b不会被释放;在执行block()的时候,如果访问a的地址,会出现坏内存访问
四.Block访问全局变量 static修饰的全局变量
Block访问全局变量和 static修饰的全局变量,不会捕获
int a = 10;
static int b = 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_dw_4ylsshdj70nf81ndx_765tfh0000gn_T_main_46ed42_mi_0,a,b);
}
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));
a = 20;
b = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
可以看到a和b定义在全局区,__main_block_impl_0里面并没有成员变量去保存a和b
在执行__main_block_func_0函数的时候,是直接访问a和b。
为什么访问局部变量会捕获,访问全局变量不会捕获
作用域问题:
局部变量只能在函数内部访问,在别的函数中是无法访问另外一个函数定义的局部变量的。
block的实现是在__main_block_func_0函数中,变量定义在其他的函数中,在__main_block_func_0中是无法访问他的函数中定的变量的,所以需要捕获;而全局变量,在任何地方都能访问,所以不需要捕获就能访问
image.png
五.Block的类型
block有三种类型,可以通过class方法或者 isa看具体类型,最终都集成自NSBlock
- NSGlobalBlock
- NSStackBlock
- NSMallocBlock
三种类型的block的内存分配情况
image.png image.png栈上的block是系统自动分配内存,自动回收内存,堆上的block由开发者申请内存,释放内存
如果是栈上的block捕获了局部变量,在其他地方调用时,会出现意向不到的结果
void (^block)(void);
void testBlock(){
int a = 10;
block = ^{
NSLog(@"a的值是:%d",a);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
testBlock();
block();
}
return 0;
}
a的值是随机的,并不是10,这是因为 ^{NSLog(@"a的值是:%d",a);} 在栈上,当testBlock函数执行完毕后,内存会被回收,全局变量block指向的内存区域的数据不再是当时的数据.
void (^block)(void);
void testBlock(){
int a = 10;
block = [^{
NSLog(@"a的值是:%d",a);
} copy];
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
testBlock();
block();
}
return 0;
}
a的值是10
NSStackBlock执行copy操作后,会将栈上的block拷贝到堆上, 变成NSMallockBlock,在堆上,内存由开发者释放,这个时候block全局变量指向堆上的block,block不会被释放
在ARC环境下,以下情况,栈block会自动调用copy,变成堆block
1.栈 block被强指针引用时会copy到堆中
//MRC
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 2;
MyBlock block = ^{
NSLog(@"%d",a);
};
block();
NSLog(@"%@",[block class]);
}
return 0;
}
在MRC环境下,block的类型是NSStackBlock,而在ARC下,block的类型是NSMallocBlock,因为有block强指针引用;
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 1;
NSLog(@"%@",[^{
NSLog(@"%d",a);
} class]);
}
return 0;
}
访问了局部变量,没有强指针引用,在MRC和ARC下,block都是NSStackBlock类型
2.栈 block作为函数的返回值会被copy到堆上
// arc环境,访问了局部变量,栈block
typedef void(^MyBlock)(void);
void testBlock(){
int a = 10;
NSLog(@"%@",[^{
NSLog(@"%d",a);
} class]);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
testBlock();
}
return 0;
}
// arc环境,访问了局部变量,但是作为了函数的返回值,堆block
typedef void(^MyBlock)(void);
MyBlock testBlock(){
int a = 10;
return ^{
NSLog(@"%d",a);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"%@",[testBlock() class]);
}
return 0;
}
3.cocoa API中的block和GCD中的block,会进行copy操作,在堆中
六.Block访问auto类型的oc对象
- 如果是栈block,不会对auto变量产生强引用。
因为栈block的内存是由系统回收的,出了作用域就会被回收,不会在作用域之外执行block,而auto变量也是出了作用域被回收,强引用oc对象的目的就是为了在执行block的时候使用的变量是正确的,栈block和auto对象的生命周期相同,所以没有必要强引用auto类型的oc对象- 如果block被拷贝到堆上: 1.会调用block内部的copy函数;2.copy函数会调用_Block_object_assigin函数; 3. _Block_object_assigin会根据 auto变量内存管理修饰符(__strong, __weak ,__unsafe_unretained)来做出对应的操作(强引用/弱引用)
如果block从堆上移除
- 调用内部的dispose函数
- dispose函数会调用内部的_Blokc_object_dispose函数.
- _Blokc_object_dispose会根据内存管理修饰符解除对auto变量的引用
1.用strong修饰
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Book *book; // strong指针
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Book *_book, int flags=0) : book(_book) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->book, (void*)src->book, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
2.用__weak修饰
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Book *__weak weakBook; // weak 指针
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Book *__weak _weakBook, int flags=0) : weakBook(_weakBook) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->weakBook, (void*)src->weakBook, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
}
网友评论