前言
Clang分析下block的底层结构
Clang看block
代码
int age = 30;
void (^yang)(void) = ^{
NSLog(@"%d", age);
};
yang();
clang
// 存在isa 说明block是个对象
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
};
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zm_558cwfjs099fbm2r8kxg8wt00000gt_T_main_bfedde_mi_0, age);
}
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, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 30;
// 指向一个地址 说明block是个指针
void (*yang)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
((void (*)(__block_impl *))((__block_impl *)yang)->FuncPtr)((__block_impl *)yang);
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
block生成的结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
};
block的实现
void (*yang)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
block的匿名函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zm_558cwfjs099fbm2r8kxg8wt00000gt_T_main_bfedde_mi_0, age);
}
block的调用
((void (*)(__block_impl *))((__block_impl *)yang)->FuncPtr)((__block_impl *)yang);
分析
- 存在isa 说明block是个对象
- 能捕获外部变量有函数的效果
- 指向一个地址 说明block是个指针
完整分析 借一张网图
LLDB调试block
将Clang编译出来的结构体copy到我们的mian文件,侨接一个结构体变量zyblock 通过lldb调试下block结构体的内部
main代码
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
};
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
int age = 30;
void (^yang)(void) = ^{
NSLog(@"%d", age);
};
struct __main_block_impl_0 *zyblock = (__bridge struct __main_block_impl_0 *)yang;
// 断点
yang();
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
断点一
断点二
开启汇编
分析
内部层级
block大括号内部的代码的第一行的地址跟FuncPtr指针指向的地址是一样的
问题
block大括号内的代码是如何存放的?
跟FuncPtr指针有什么关系?
我们带着问题往下看
源码和cpp代码对照
1、 block 捕获的外部变量
2、 定义的block yang
3、 调用block
生成 block ^{}
// =右边的 ^{} 被编译成 ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age))
void (^yang)(void) = ^{ NSLog(@"%d",age);};
void (*yang)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
// 讲 yang 指向 __main_block_impl_0结构体地址
分析
- =右边的 ^{} 被编译成 ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age))
- 将yang 指向 __main_block_impl_0结构体地址
- 传递了__main_block_func_0 __main_block_desc_0_DATA age 3个参数
- 将地址强转位 ((void (*)()) 类型
- 将地址赋给 yang 指针
__main_block_impl_0
c++中 如果struct 有自定义构造方法且构造方法后面跟列表初始化 只能写到实现中
定义
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
};
实现
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
// 自定义构造方法 : 这个符号表示 后面跟了初始化列表
// 就是把外界传入的值先给 _age 然后 age = _age 这些属于c++ 语法
__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;
}
};
__main_block_func_0
// 编译会把block块的代码放到这个函数里面
// 函数加static关键字 函数地址固定 函数在全局区
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_zm_558cwfjs099fbm2r8kxg8wt00000gt_T_main_9da3e9_mi_0, age);
}
参数传递如图:
分析
-
__main_block_func_0函数存放的是block大括号里面的代码
该函数被直接赋值给__main_block_impl_0构造函数的第一个参数fp指针
fp又赋值给__block_impl结构体中的FuncPtr变量 -
FuncPtr变量指向__main_block_func_0函数 既block大括号内的代码
也就是你在block 内部写不写代码 编译器都会创建一个 fun0函数
__main_block_desc_0
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { , sizeof(struct __main_block_impl_0)};
参数传递如图:
分析
结构体有两个成员变量,同时定义了一个结构体变量__main_block_desc_0_DATA并对其赋值,reserved赋值为0,Block_size赋值为sizeof(struct __main_block_impl_0) 也就是结构体的内存大小
block 调用
__block_impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
分析
- 结构体有4个成员变量第一个成员变量为isa指针,我们知道这是oc对象(实例、类、元类)的专属标志,因为__block_impl 在__main_block_impl_0 内部 所以__main_block_impl_0是一个oc对象,而oc对象的本质就是在内存中为结构体
- yang是一个 __main_block_impl_0 结构体指针
- yang是一个结构体指针 所以访问成员FuncPtr使用 ->
问题
1、FuncPtr 并不是__main_block_impl_0 结构体的成员变量 他是__block_impl的成员变量 不应该是 yang->impl.FuncPtr吗?
2、yang进行了强制转换(__block_impl *)yang,而__block_impl结构体中是存在FuncPtr变量的 但这完全是两个不同的结构体,也不能强制转换引用啊?
父指子强转类型
c/c++经常会使用父指子的访问方式
如果父的前几个成员和子的前几个成员内存布局和顺序一致,可以使用父指子
如图
__main_block_impl_0结构体在__block_impl结构体的大小范围内跟__block_impl结构体是完全重合的(其实就是同一片内存)只不过__main_block_impl_0结构体大小要比__block_impl结构体大 经过强制转换后block完全可以直接引用FuncPtr成员变量
侨接结构体
OC强转类型的使用
假设我们通过函数指针来调用 funyang 函数
代码
struct Car {
NSString *isa;
int price;
int year;
void *ptr;
}car;
struct Person {
struct Car car;
int age;
}person;
void funyang(struct Car *xmcar) {
NSLog(@"1");
}
找到funyang函数地址我们有下面几种方式
- (void)test1 {
person.car.ptr = (void *)funyang;
struct Person *yang = &person;
void *p1 = &funyang;
void *p2 = person.car.ptr;
void *p3 = yang->car.ptr;
void *p4 = ((struct Car *)yang)->ptr;
}
输出
(lldb) p p1
(void *) $0 = 0x000000010bcabd70
(lldb) p p2
(void *) $1 = 0x000000010bcabd70
(lldb) p p3
(void *) $2 = 0x000000010bcabd70
(lldb) p p4
(void *) $3 = 0x000000010bcabd70
(lldb)
这里我们为了帮助理解上面的block 我们选择强转方式
// yang 本身是 Person结构体指针 相当于上面的 __main_block_impl_0
// 讲yang转化为 Car结构指针 相当于上面的 __block_impl
((struct Car *)yang)->ptr;
使用
- (void)test {
// 给结构体成员赋值
person.car.isa = @"模拟block";
person.car.price = 20;
person.car.year = 2021;
// 讲funyang的函数地址 赋给 ptr
person.car.ptr = (void *)funyang;
person.age = 30;
// 下面我通过指针访问 ptr
struct Person *yang = &person;
// 正常访问是这样
yang->car.ptr;
// 如果我强转成Car之后访问是这样
((struct Car *)yang)->ptr;
/*
我们要访问的函数 void funyang(struct Car *xmcar)
函数类型 void (*)(struct Car *)
参数类型 (struct Car *)
*/
// 用p4指向ptr ptr是函数指针 存的是funyang函数地址
void *p4 = ((struct Car *)yang)->ptr;
// 把p4强转成一个函数类型 ((void (*)(struct Car *)) p4);
((void (*)(struct Car *)) p4);
// 调用函数并传入参数 参数类型是funyang函数的参数类型
((void (*)(struct Car *)) p4)((struct Car *)yang);
// 把p4去掉
((void (*)(struct Car *)) ((struct Car *)yang)->ptr)((struct Car *)yang);
我们还可以这么访问
// 我们还可以在用一个结构体指针 再次间接访问
struct Car *xiaoyang = (struct Car *)yang;
((void (*)(struct Car *))((struct Car *)xiaoyang)->ptr)((struct Car *)yang);
输出
2021-11-24 20:40:10.167551+0800 07_本质[61067:32686116] 1
2021-11-24 20:40:10.167699+0800 07_本质[61067:32686116] 1
分析
yang指针调用1次 xiaoyang指针调用1次
如果要访问的函数参数是Person类型
void funyang(struct Person *xmcar) {
NSLog(@"1");
}
调用
struct Person *nb = &person;
nb->car.ptr = (void *)funyang;
((void (*)(struct Person *)) nb->car.ptr)(nb);
((void (*)(struct Person *)) (nb->car).ptr)(nb);
void * p5 = nb->car.ptr;
((void (*)(struct Person *)) p5)((struct Person *) nb);
((void (*)(struct Person *)) nb->car.ptr)((struct Person *) nb);
理解了上面的oc例子 再看 block代码就简单多了,其实就是
代码
void (^yang)(void) = ^{};
yang();
编译生成
void (*yang)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
((void (*)(__block_impl *))((__block_impl *)yang)->FuncPtr)((__block_impl *)yang);
简化
void (*yang)(void) = &__main_block_impl_0(参数1,参数2,参数3)
yang->FuncPtr(参数)
举例
在苹果的源码里面,经常使用这样的方式,理解了block 其他地方也一样 我们以dyld源码为例
block内存分布
图一源码
图二 查看Block匿名函数地址
分析
1、找到p对象地址
2、找到结构体__main_block_impl_0地址
3、找到__main_block_impl_0结构体中 _FuncPtr 地址
图三 查看 block结构体大小 和 i捕获的auto变量 int a 的值
分析
1、找到p对象地址
2、p对象偏移8字节找到结构体__main_block_impl_0地址
3、_main_block_impl_0首地址也是__block_impl结构体地址 __block_impl占24字节
4、_main_block_impl_0首地址偏移24字节是__main_block_desc_0结构体地址__main_block_desc_0 占据8字节
5、__main_block_desc_0首地址偏移4字节 就是Blocksize 地址 blocksize = 24+8+4 = 36
6、_main_block_impl_0首地址偏移 32字节就是 int a的地址
结论
- block本质是一个oc对象,以block_impl_0结构体形式存放在内存中
- block本身也是一个指针,存放的是该结构体的内存地址
- 遇到 ^{} 编译器内部会生成一个 fun0函数 把我们写的代码块放到 fun0函数中 ,然后把fun()函数的入口地址 存放到 block_impl_0 结构中的成员变量 FuncPtr 中
- FuncPtr Func代表函数 prt代表指针地址 ptr point adress
- block_impl是函数调用结构体 我们使用block() 进行block调用时候其实就是找 func地址