Block本质
- block本质上也是一个OC对象,它内部也有个isa指针
- block是封装了函数调用以及函数调用环境的OC对象
定义一个block
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
void(^block)(void) = ^(void){
NSLog(@"hello-world");
};
block();
}
return 0;
}
通过命令行执行
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
获得main.cpp文件查看block的底层实现
编译后main.cpp里main函数代码如下
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//定义block变量
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//执行block内部的代码
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
从上面代码中可以看出
__main_block_impl_0 是我们所定义的block
__main_block_func_0 block内部代码封装成函数
__main_block_desc_0_DATA 定义的block所占内存的结构体
__main_block_impl_0结构体如下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//构造函数 类似OC init函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; //block类型
impl.Flags = flags;
impl.FuncPtr = fp;// 执行函数的地址
Desc = desc; //存储 __main_block_desc_0(0,sizeof(__main_block_impl_0))的值
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;//指针指向了block内部执行函数的地址
};
__main_block_func_0
// block 内部代码封装成函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2v_yzvmxk793t31txq41bn50r1r0000gn_T_main_f08b1f_mi_0);
}
__main_block_desc_0_DATA
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转化成__main_block_impl_0结构体对象,赋值给变量block,传入参数是__main_block_func_0和__main_block_desc_0_DATA来执行__main_block_impl_0的构造函数,__main_block_desc_0_DATA函数赋值给__main_block_impl_0->FuncPtr,执行函数是block->FuncPtr(block),删除冗余代码之前是((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);,那么为什么block可以直接强制转化成__block_impl呢?因为__main_block_impl_0结构体的第一行变量是__block_impl,相当于__main_block_impl_0的内存地址和__block_impl的内存地址一样,强制转化也不会有问题。
block的变量捕获
- 为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
auto
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
int age = 10;
void (^block)(void) = ^(void){
NSLog(@"age is %d",age);
};
age = 20;
block();
}
return 0;
}
main.cpp 代码
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
age = 20;
((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 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;
}
};
可以看出是直接将age 传进block的构造函数中 传递方式为值传递。
static
int age = 10;
static int height = 20;
void (^block)(void) = ^(void){
NSLog(@"age is %d height is %d",age,height);
};
age = 20;
height = 30;
block();
main.cpp
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
static int height = 20;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
age = 20;
height = 30;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
可以看出height传值是用&取得指针地址符
在__main_block_impl_0结构体中 也是以*开头指针地址
所以height的值会发生改变。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
int *height;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
全局变量
全局变量不用捕获到__main_block_impl_0结构体当中,直接访问全局变量。
__block修饰符
- __bllock可以用于解决block内部无法修改auto变量值得问题
- __block 不能修饰全局变量、静态变量(static)
- 编译器会将__block变量包装成一个对象
code
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
__block int age = 10;
void(^block)(void) = ^(void){
age = 20;
NSLog(@"age is %d",age);
};
block();
}
return 0;
}
main.cpp
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
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;
}
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
在block内部多了一个指向__Block_byref_age_0类型结构体的age指针。我们发现int类型的age在这个结构体内部了。
那也就是说,__block修饰的变量,编译器会把它包装成一个对象,然后我们的这个成员变量放到了这个对象的内部。
我们观察一下这个__Block_byref_age_0内部,这些变量可能有疑惑的也就是这个__forwarding。他是一个指向这个结构体自身的指针。而且我们还可以看出来在打印age的时候,是也是通过__forwarding调用的age(age->__forwarding->age)
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这个结构体中也多了两个指针,这是与内存管理有关的函数。