block
文章目录
如何通过终端clang生成源码cpp文件
之前在学习block中学习的比较浅,只看了oc高级编程书上有的源码,学习的比较浅,这节来对之前的作以补充。
首先来看看怎么将oc文件生成cpp的源码文件:
将项目大文件里的二级文件,打开终端 cd 并将此文件拖入:
然后再输入:
clang -rewrite-objc main.m
回车打开之前的项目文件,即可看到生成了一个cpp文件,打开就是oc的c++源码。
打开后发现有60000多行,很抽象,关键部分看最前面和最后面即可。
block实质
我们现在时文件中写一个最简单的block,看看源码是什么:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
void (^blk)(void) = ^{
printf("Block\n");
};
blk();
}
return 0;
}
源码:
//经过clang转换后的C++代码
struct __block_impl {
void *isa;//指向所属类的指针
int Flags;//标志性参数,暂时没用到所以默认为0
int Reserved;//今后版本升级所需的区域大小。
void *FuncPtr;//函数指针,指向实际执行的函数,也就是block中花括号里面的代码内容。
};
struct __main_block_impl_0 {
struct __block_impl impl;//上面点1中的结构体的变量
struct __main_block_desc_0* Desc;//上面点2中的结构体的指针
__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; //今后版本升级所需区域的大小(一般填0)
size_t Block_size; //Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
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;
}
我们来分开说一下每部分的意义:
1.第一个结构体
struct __block_impl {
void *isa;//指向所属类的指针
int Flags;//标志性参数,暂时没用到所以默认为0
int Reserved;//今后版本升级所需的区域大小。
void *FuncPtr;//函数指针,指向实际执行的函数,也就是block中花括号里面的代码内容。
};
- 结构体的名称:impl即implementation的缩写,换句话说这一部分是block的实现部分结构体。
- void *isa:声明一个不确定类型的指针,用于保存Block结构体实例。
- int Flags:标识符。
- int Reserved:今后版本升级所需的区域大小。
- void *FuncPtr:函数指针,指向实际执行的函数,也就是block中花括号里面的代码内容。
2.在介绍struct __main_block_impl_0结构体之前,先介绍一下这个结构体里的static struct __main_block_desc_0结构体
这个结构体在上面一坨源码的最底下:
static struct __main_block_desc_0 {
size_t reserved; //今后版本升级所需区域的大小(一般填0)
size_t Block_size; //Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
- 第一个成员变量指的是今后版本升级所需区域的大小(一般填0)。
- 第二个成员变量是Block的大小。
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};:
1⃣️.这就是和我们平时用结构体一样,在定义完最后写一个结构体实例变量,变量名就是__main_block_desc_0_DATA。
2⃣️.其中reserved为0,Block_size是sizeof(struct __main_block_impl_0)。
3.接下来看struct __main_block_impl_0结构体
struct __main_block_impl_0 {
struct __block_impl impl;//上面点1中的结构体的变量
struct __main_block_desc_0* Desc;//上面点2中的结构体的指针
__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;
}
};
- 第一个成员变量是impl,也就是上面点1中的结构体的变量。
- 第二个成员变量是Desc指针,就是上面点2中的结构体的指针。
- 剩下的代码是:初始化含有这些结构体的__main_block_impl_0结构体的构造函数,给结构体里的元素赋值。
4.然后是static void __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself {
printf("Block\n");
}
这个就比较简单了,Blcok执行的实际代码块。也是点3中fp指针指向的函数。括号中的参数__cself是相当于OC语言版的self,代表的是Block本身。
5.最后来看main函数
int main(int argc, const char * argv[]) {
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;
}
这两句代码不长但是比较恶心,我们先来看第一句,把强制类型转换去一下试试:
//加了个临时变量temp,看着更方便点
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;
该源代码将__main_block_impl_0结构体类型的自动变量,也就是栈上生成的__main_block_impl_0结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量blk。
第二行代码就是相当于源代码中的blk(),即使用该Block部分。去掉转换部分就是:
(*blk->impl.FuncPtr)(blk);
这是使用函数指针调用函数。由Block语法转换的__main_block_impl_0函数的指针被赋值成员变量FunPtr中。
截获自动变量
int main(int argc, const char * argv[]) {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{
printf(fmt, val);
};
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;
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;
int val = __cself->val;
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[]) {
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));
return 0;
}
与上次不同的是在Block语法表达式中使用的自动变量被当作成员变量追加到了__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)
初始化时自动变量fmt和val进行了赋值操作:
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
fmt = "val = %d\n";
val = 10;
在转换后的代码中,截获到__main_block_impl_0结构体实例的成员变量上的自动变量,这些变量在Block语法表达式之前被声明定义,所以之后即使改变自动变量的值也不会对Block语法中的内容有所变化。
总的来说,所谓“截获自动变量值”意味着在执行Block语法时,Block语法表达式所使用的自动变量值被保存到Block的结构体实例(即Block自身中)。
总结一下就是说:
调用__main_block_impl_0的构造函数,构造函数正常有两个参数,func_0和desc_0,在截获自动变量时,会把需要截获的自动变量也放入参数列表中,同时__main_block_impl_0中也会增加两个成员变量a,b,构造函数带参数就是自动给这两个成员变量赋值。在调用func_0时,直接通过__cself(相当于self,其传递的参数也是其本身)的a、b(此时的a,b就是成员变量中的,因为上面的构造函数已经赋值了)
全局变量和静态变量的截获
我们以以下代码为例:
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;
}
我们主要来看下这段代码:
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_val是从__cself中获取的值
再来看看上面的一段代码:
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
- 也就是说我们使用一个指针来保存的静态局部变量
- 它会直接保存该变量的地址,之后的操作也是直接对该值本身进行操作,而不是向之前截获的那些变量,等于是重新开辟空间进行保存
产生这个问题的原因:
原因在于,我们的静态变量是存在数据区的,在程序结束前它其实一直都会存在,之所以会被称为局部,只是说出了作用域无法调用到它了,并不是说这块数据不存在了。因此我们只要自己准备好一个指针,保证出了作用域依然能调用到他就行;而对于自动变量,它们真正的问题在于一但出了作用域,直接被释放了,所以要在结构体里开辟空间重新存放,进行值传递
__block说明符
来看书上的例子:
int main(int argc, const char * argv[]) {
__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;
(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, BLOCK_FIELD_IS_BYREF);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src {
_Block_object_dispose((void*)src->val, 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[]) {
__Block_byref_val_0 val = {
0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
10
};
blk = &__main_block_impl_0(
__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
return 0;
}
和没加修饰符的比较来说,主要有两处不同:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
在第一个结构体中,我们发现多了一个__Block_byref_val_0 *__forwarding,这个相当于指向该结构体本身的一个指针。
第二个地方:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val;
(val->__forwarding->val) = 1;
printf("val = %d\n", (val->__forwarding->val));
}
在这里我们发现打印的并不是__Block_byref_val_0 *val而是(val->__forwarding->val)。
最后来看一下主函数:
int main(int argc, const char * argv[]) {
__Block_byref_val_0 val = {
0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
10
};
blk = &__main_block_impl_0(
__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
return 0;
}
看下其中赋值部分:
__Block_byref_val_0 val = {
0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
10
};
这个__block变量val变为了__Block_byref_val_0结构体变量。通过调用 static void __main_block_func_0函数(通过__Block_byref_val_0结构体成员变量__forwarding访问成员变量val),将10赋给val。
iOS开发“强弱共舞”——weak和strong配套使用解决block循环引用问题
__weak是为了解决循环引用
如果一个对象A持有了一个block,同时block内又持有了对象A,为了解决循环引用我们要在用__weak修饰完对象A后再去持有它,这样就解决了循环引用。
__strong可以防止block持有的对象提前释放
我们用GCD延迟方法打印self的信息:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self dismissViewControllerAnimated:YES completion:nil];
__weak typeof(self) weakSelf = self;
void (^Block) (void) = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", weakSelf);
});
};
Block();
}
输出结果:
点击屏幕,当前控制器消失,同时被销毁掉,5秒后打印的weakSelf就是一个(null),而我们如果在block内使用__strong后就能保证再打印完strongSelf之后再释放当前控制器。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self dismissViewControllerAnimated:YES completion:nil];
__weak typeof(self) weakSelf = self;
void (^Block) (void) = ^{
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", strongSelf);
});
};
Block();
}