iOS 底层解析-----Block (上)
2019-03-06 本文已影响48人
Mr丨Yang
本章主要分析一下三个结论 (先给出结论,主要请自己阅读给出的源码,语言无法解释清楚)
一 :Block的本质
1.block 本质上是一个OC对象,它内部也有个isa指针
2.block 是封装了函数调用以及函数调用环境的OC对象
三:Block的类型
block有三种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
1. __NSGlobalBlock __ (_NSConcreteGlobalBlock)
2. __NSStackBlock __ (_NSConcreteStackBlock)
3. __NSMallocBlock __ (_NSConcreteMallocBlock)
一 :Block的本质
int main(int argc, const char * argv[]) {
@autoreleasepool {
//如下block代码 编辑之后
void (^block)(void) = ^{
NSLog(@"Hello, World!");
};
block();
}
return 0;
}
//编译之后的block代码 抽取核心代码
// 1. main中的block编译之后
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 定义block变量
void (*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA
);
// 执行block内部的代码
block->FuncPtr(block);
}
return 0;
}
// 2. __main_block_impl_0函数第一个参数:__main_block_func_0, 封装了block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c60393_mi_0);
}
//3. __main_block_impl_0函数第二个参数:_main_block_desc_0 代表了Block在内存中占用的大小
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)};
//4. __main_block_func_0中 __main_block_impl_0 结构体如下
struct __main_block_impl_0 {
struct __block_impl impl;//这个地方存放的是指针对应的值 也就是存在着struct __block_impl结构体 里面包含有isa指针
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;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
-------------------------------------分割线-----------------------------------
//block 带参数
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(int, int) = ^(int a, int b){
NSLog(@"Hello, World! - %d %d", a, b);
};
block(10, 20);
}
return 0;
}
//编译之后
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));
block->FuncPtr(block, 10, 20);
}
return 0;
}
//调用的方法函数 将参数作为自己的参数进行传递
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_87bc8b_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)};
二:Block的变量捕获(capture) 从代码分析
//局部变量解析
void (^block)(void);
void test()
{
//在test方法中,int 变量是局部变量 默认属性为auto 可以不写
// auto:自动变量,离开作用域就销毁
// auto int age = 10;
int age = 10;
//增加static 作为局部变量,在test 方法调用结束后所占内存并不会销毁
static int height = 10;
block = ^{
NSLog(@"age is %d, height is %d", age, height);
};
age = 20;
height = 20;
}
//运行之后看结果 age is 10, height is 20
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block();
}
return 0;
}
看编译之后的代码
void (*block)(void);
-------重点-------
//block将 age height 作为结构体的一部分
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
int age; //这个地方的age 因为是auto 所以第一次传进来的时候,age初始的值10被捕获到,所以age 最终为10
int *height;//这个height 为static 它也会捕获但是它捕获的是height的内存中的指针,并不在意你初始化值,在运行block()之后,它会去取指针对应的值,也就是20
__test_block_impl_0(void *fp, struct __test_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;
}
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
int *height = __cself->height; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*height));
}
static struct __test_block_desc_0 {
size_t reserved;
size_t Block_size;
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)};
void test()
{
int age = 10;
static int height = 10;
block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height));
age = 20;
height = 20;
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
test();
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
-------------------------------分割线-------------------
//全局变量解析
int age_ = 10;
static int height_ = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^{
NSLog(@"age is %d, height is %d", age, height);
};
age_ = 20;
height_ = 20;
block();
}
return 0;
}
//编译之后
int age_ = 10;
static int height_ = 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_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c60393_mi_0,age_,height_ );
}
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)};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA
);
age_ = 20;
height_ = 20;
block->FuncPtr(block);
}
return 0;
}
总结: 因为它是全局的,可以直接取值,所以block 不需要捕获
三:Block的类型
注意:该段代码验证要将ARC编译环境改为MRC
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
//Global:没有访问auto变量
void (^block1)(void) = ^{
NSLog(@"Hello");
};
//Stack:访问了auto变量
void (^block2)(void) = ^{
NSLog(@"Hello - %d", age);
};
NSLog(@"%@ %@ %@", [block1 class], [block2 class]);
打印结果:_NSGlobalBlock_ _NSStackBlock_
}
return 0;
}
---------------代码片段二-------------------------
void (^block)(void);
void test()
{
int age = 10;
//Stack:访问了auto变量
block =^{
NSLog(@"block------%d",age);
}
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
//问题:Stack存在于栈中,栈中的数据可能会销毁,所以age取值可能已经销毁掉,取值会存在错误(MRC环境)
block();
}
return 0;
}
以上代码问题如果给block 增加copy字段会将值从栈中捕获到堆中,取值会变成正常
void test()
{
int age = 10;
//增加copy 变成Malloc
block =[^{
NSLog(@"block------%d",age);
} copy];
}
以上代码表示的三种类型 在内存中的分配如下图
.text区 放代码段
.data区 一般存放全局变量
存放在堆内存中,相当于alloc出来的,需要程序员申请,也需要程序员自己管理内存
存放在栈中,系统自动分配,调用完成之后就销毁
总结如下
Block类型.png