文章目录
关于我的仓库
- 这篇文章是我为面试准备的iOS基础知识学习中的一篇
- 我将准备面试中找到的所有学习资料,写的Demo,写的博客都放在了这个仓库里iOS-Engineer-Interview
- 欢迎star??
- 其中的博客在简书,CSDN都有发布
- 博客中提到的相关的代码Demo可以在仓库里相应的文件夹里找到
前言
- 本文主要是对于《高级编程》类似于总结的学习笔记
- 其实这一部分本质上就是根据多个block的源代码实例,分析其背后真正的实现原理
- 在这一块,我会以例子带入来讲,尽可能把每一个block的源码讲清楚
- 这一块难就难在它的顺序很乱其实,这个知识点会涉及到好几个别的知识点,这些知识点我就统一写在后面,大家看到看不懂的地方,看目录翻后面的就行
- 每一部分的源代码都是现场手动生成
准备工作
- 阅读《Objective-C 高级编程》中的p.91 ~ 136
最简单block
//OC代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^blk)(void) = ^{
printf("Block\n");
};
blk();
}
return 0;
}
//经过clang转换后的C++代码
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 {
printf("Block\n");
}
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 (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
- 我们把代码分成几块,一块一块分析
__block_impl结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
- 名字中的impl即implementation的缩写,换句话说这一部分是block的实现部分结构体
- void *isa:
- C语言中void * 为 “不确定类型指针”,void *可以用来声明指针。
- 看到isa就会联想到之前在objc_class结构体,因此我们的block本质上也是一个对象【而且是个类对象】
- 我们知道实例对象->类对象->元类构成了isa链中的一条,而这个__block_impl结构体占据的是中间类对象的位置
- 实例对象应该是生成的block变量,个人认为
- 因此这里的isa指针会指向元类,这里的元类主要是为了说明这个块的存储区域【详见:Block存储域&&Block元类】
- int Flags:
- 标识符,在实现block的内部操作时会用到
- int Reserved:
- 注明今后版本升级所需区域大小
Reserved - 一般就是填个0
- 注明今后版本升级所需区域大小
- void *FuncPtr:
- 函数指针
- 实际执行的函数,也就是block中花括号里面的代码内容,最后是转化成一个C语言函数执行的
struct __main_block_impl_0结构体
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;
}
};
- 该结构体才是块的完整结构
- struct __block_impl impl:
- 就是上面的结构体,作为块的实现部分
- struct __main_block_desc_0* Desc:
- 这里的desc即description,作为块的补充信息
- 下面分析
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)- 这是该结构体的构造函数
- 其中fp是实际执行的C语言函数指针
- int flags = 0是C++中的缺省参数,表示默认是0
- 具体内容就是对impl中相应的内容进行赋值,要说明的是impl.isa = &_NSConcreteStackBlock这个参看Block存储域&&Block元类
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
static void __main_block_func_0(struct __main_block_impl_0 *__cself {
printf("Block\n");
}
- 这一块就是Blcok执行的实际代码块,就如我上面所说,它被转换为了一个C++函数
- 它也是上面的fp函数指针指向的内容
- 这里要注意的是传入的这个
__cself参数,他其实就是C语言版的self,代表的就是block本身,毕竟其数据类型就是struct __main_block_impl_0 - 当然从这段代码看不出来传入的cself有什么用,因为我们的代码就只输出一段话,没有用到捕获的变量,后面会讲到cself到底怎么用
static struct __main_block_desc_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)};
- size_t reserved:
- 今后版本升级所需区域大小
- 一般就填0
- size_t Block_size:
- Block大小
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};:- 这就是和我们平时用结构体一样,在定义完最后写一个结构体实例变量,变量名就是
__main_block_desc_0_DATA - 其中reserved为0,Block_size是
sizeof(struct __main_block_impl_0)
- 这就是和我们平时用结构体一样,在定义完最后写一个结构体实例变量,变量名就是
主函数【blk实际调用】
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
- 正常人看到这一段都晕了?
- 我们把所有强制类型转换去掉,看个正常人看的懂的版本:
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 这一段就是通过构造函数构造一个__main_block_impl_0结构体赋值给blk变量
// 翻译如下
struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 blk = &temp;
// 下面是调用block中函数的过程,我们可以看到我们要调用的其实就是FuncPtr这个函数指针指向的函数
// 查看__main_block_func_0的参数,发现就是我们上面研究的cself
// 所以,该调用翻译如下:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
blk->FuncPtr(blk);
- 上面值得注意的是,在使用构造函数的时候,我们传入的参数一个是我们的block函数指针,一个是在定义结构体的时候定义的__main_block_desc_0_DATA
- 好,这就是不涉及截获自动变量的最简单Block分析,下面我们来看捕获自动变量的情况
截获自动变量的block
-
这里我们又需要看一波C++源码,这里面会有很多一样的代码,我就不做分析了
-
GOGOGO
int main(int argc, const char * argv[]) {
@autoreleasepool {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{
printf(fmt, val);
};
val = 2;
fmt = "THESE VALUES WERE CHANGED. val = %d\n";
blk();
}
return 0;
}
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;
const char *fmt;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt, val);
}
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;
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
val = 2;
fmt = "THESE VALUES WERE CHANGED. val = %d\n";
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
新的__main_block_impl_0
- 我们会看到
__block_impl结构体没有任何变化,而__main_block_impl_0多了点东西
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 我们的fmt,val这两个被block截获的自动变量被放入到该结构体当中,同时构造函数也发生了变化,构造时要给fmt,val赋值
- 这里我们就能大概猜出截获自动变量的原理了,自动变量会被存入block结构体
- 在这里也要注意我们等于是使用了一个长得一模一样,保存在结构体里的数来进行的赋值操作,所以我们不能对它进行赋值操作,因为我们操作的只能是我们自己建的数据,而不会是我们真正的变量
新的__main_block_func_0函数
- 这次就会用到上面说的cself了
static void __main_block_func_0(struct __main_block_impl_0 *__cself {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt, val);
}
- 这里在实际调用时,我们还是只需要传入一个cself,我们就会看到在函数内部,我们进行操作的拿来printf不是原来的fmt和val,而是通过块结构体保存的这两个值
没有截获自动变量,而是使用静态变量,全局变量情况
- 在前一篇文章,我们了解到,对于截获的自动变量,不能直接修改它的值,而对于静态变量,全局变量时OK的,我们来看下对于这些变量block是怎么处理的
int global_val = 10; // 全局变量
static int static_global_val = 20; // 静态全局变量
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int static_val = 30; // 静态局部变量
void (^myLocalBlock)(void) = ^{
global_val *= 1;
static_global_val *= 2;
static_val *= 3;
printf("static_val = %d, static_global_val = %d, global_val = %d\n",static_val, static_global_val, global_val);
};
myLocalBlock();
}
return 0;
}
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
int global_val = 10;
static int static_global_val = 20;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
printf("static_val = %d, static_global_val = %d, global_val = %d\n",(*static_val), static_global_val, global_val);
}
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;
static int static_val = 30;
void (*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);
}
return 0;
}
新的__main_block_impl_0
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 我们发现全局变量,静态全局变量,我们的Block都没有用结构体去特地保存它
- 只有对于我们的静态局部变量会来保存,但这里要注意,我们使用的不是int static_val,而是int *static_val
- 也就是说我们使用一个指针来保存的静态局部变量
- 它会直接保存该变量的地址,之后的操作也是直接对该值本身进行操作,而不是向之前截获的那些变量,等于是重新开辟空间进行保存
新的__main_block_func_0函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
printf("static_val = %d, static_global_val = %d, global_val = %d\n",(*static_val), static_global_val, global_val);
}
- 这里我们使用的global_val以及static_global_val都是直接调用,只有static_val是通过指针获取值,进行修改
- 那么这种做法看起来很不错,为什么在截获自动变量的时候我们不用指针传值而是要用值传值呢?
- 原因在于,我们的静态变量是存在数据区的,在程序结束前它其实一直都会存在,之所以会被称为局部,只是说出了作用域无法调用到它了,并不是说这块数据不存在了。因此我们只要自己准备好一个指针,保证出了作用域依然能调用到他就行;而对于自动变量,它们真正的问题在于一但出了作用域,直接被释放了,所以要在结构体里开辟空间重新存放,进行值传递
使用__block修饰符的情况
- 在前一篇文章,我们了解到,对于截获的自动变量,不能直接修改它的值,而对于静态变量,全局变量时OK的,我们来看下对于这些变量block是怎么处理的
__block 修饰符类似于static、auto、register说明符,它们用于指定将变量值设置到哪个存储域中。例如auto表示作为自动变量存储在栈中,static表示作为静态变量存储在数据区中。
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int val = 10;
void (^blk)(void) = ^{
val = 1;
printf("val = %d\n", val);
};
blk();
}
return 0;
}
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
printf("val = %d\n", (val->__forwarding->val));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
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_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
- 当你觉得block的代码已经够多的时候,
__block源代码反手给你个超级加倍告诉你什么才是多?
__Block_byref_val_0结构体
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
- 对于使用
__block修饰的变量,不管在块里有没有使用,都会相应的给他生成一个结构体 - 这里的isa指针默认都是传的空,但实际上是对于C语言基础数据类型会是0,因为他们不是对象没有所属类,而对于对象其实isa指针指向的就是所属类
- 但为什么看源码会是全部都赋值为0呢,因为OC是一门动态语言,运行的时候才会确定下来,不放心的话可以通过class方法查看下
- 关于
__forwarding参看__block的拷贝部分 - flags标志符位
- size大小
- val变量本身
__main_block_impl_0结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 这部分值得注意的是,对于我们的
__Block_byref_val_0结构体,我们同样是用一个指针去保存,这么做的原因是通过__block修饰的变量可能会被不止一个block使用,使用指针可以保证其可以被多个block调用
__main_block_func_0函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
printf("val = %d\n", (val->__forwarding->val));
}
- 这里看到我们用val截获下来的就是一个
__Block_byref_val_0结构体了,对它进行赋值的时候需要通过forwarding指针进行 - 下面我们先看下主函数
主函数
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
- 主要关注下
__Block_byref_val_0结构体的赋值
__Block_byref_val_0 val = {
(void*)0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
10
};
- isa为0上面解释过了,forwarding为自身的地址,flags为0


454

被折叠的 条评论
为什么被折叠?



