狮子头镇楼
参考资料:
《Objective-C 高级编程》干货三部曲(二):Blocks篇
俗话说好记性不如烂笔头,参考了大佬的智慧,看完狮子头后总结的学习笔记。如有侵权请友情告知。
1、什么是Blocks
Blocks是C语言的扩充功能:带有局部变量的匿名函数;
Blocks中将带有局部变量的匿名函数部分称为“Block literal”,或简称“Block”;
“带有局部变量的匿名函数”这一概念并不仅指Blocks,它还存在于其他程序语言中,在计算机科学中,此概念也称为必包。
2、Blocks模式
Block语法:
Block例子:
//1、可用 typedef 给Block定义别名
typedef int (^blk_t)(int);
int blockDemo() {
blk_t blk = ^(int count) {
return count + 1;
};
int a = blk(6);
printf("a = %d\n", a);
//2、Block会截获所使用的局部变量值,即保存该局部变量的瞬间值;
int b = 111;
//3、使用附有 __block 说明符的局部变量可在Block中赋值,该变量称为 __block 变量
__block int c = 222;
void (^blk_b)(void) = ^ {
printf("b1 = %d\n", b);
printf("c1 = %d\n", c);
c += b;
};
b = 222;
blk_b();
printf("b2 = %d\n", b);
printf("c2 = %d\n", c);
return 0;
}
输出结果是:
3、Blocks的实现
3.0 OC转C的方法,可通过 clang 指令转换源代码
转换后可以在该文件夹得到一个:block.cpp,转换得到的C++代码就在其中。
3.1 Block语法转换
OC代码:
int mainDemo() {
void (^blk)(void) = ^{
printf("Block\n");
};
blk();
return 0;
}
转换成C++后:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __mainDemo_block_impl_0 {
struct __block_impl impl;
struct __mainDemo_block_desc_0* Desc;
__mainDemo_block_impl_0(void *fp, struct __mainDemo_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//通过 Blocks 使用的匿名函数实际上被作为简单的C语言函数来处理
//参数 __cself 为指向 Block 值的变量
static void __mainDemo_block_func_0(struct __mainDemo_block_impl_0 *__cself) {
printf("Block\n");
}
static struct __mainDemo_block_desc_0 {
size_t reserved;
size_t Block_size;
} __mainDemo_block_desc_0_DATA = {
0,
sizeof(struct __mainDemo_block_impl_0)
};
int mainDemo() {
void (*blk)(void) = ((void (*)())&__mainDemo_block_impl_0((void *)__mainDemo_block_func_0,
&__mainDemo_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
// 去掉转换部分:
// struct __mainDemo_block_impl_0 tmp = __mainDemo_block_impl_0(__mainDemo_block_func_0, &__mainDemo_block_desc_0_DATA);
// struct __mainDemo_block_impl_0 *blk = &tmp;
return 0;
}
3.2 Objective-C类和对象的实质
//“id”这一变量类型用于存储OC对象,它的声明如下:
typedef struct objc_object {
Class isa;
} *id;
//id 为 objc_objc 结构体的指针类型
//Class 为 objc_calss 结构体的指针类型
typedef struct objc_class *Class;
//objc_class 的声明如下:
struct objc_class {
Class isa;
};
//objc_object 结构体和 objc_class 结构体是在各个对象和类的实现中使用的最基本的结构体。
举个例子,定义一个OC类:MyObject
@interface MyObject : NSObject {
int val0;
int val1;
}
@end
基于 objc_object 结构体,MyObject类的对象结构体如下:
struct MyObject {
Class isa;//通过成员变量 isa 保持该类的结构体实例指针
int val0;
int val1;
}
那么MyObject的实质:
各类的结构体就是基于 objc_class 结构体的 class_t 结构体。class_t 结构体的声明如下:
struct class_t {
struct class_t *isa;
struct class_t *superclass;
Cache cache;
IMP *vtable;
uintptr_t data_NEVER_USE;
};
//该结构体实例会持有声明的成员变量、方法的名称、方法的实现(即函数指针)、属性以及父类的指针
3.3 Block的实质:为Objective-C对象
//Block结构体
struct __mainDemo_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __mainDemo_block_desc_0* Desc;
};
// __mainDemo_block_impl_0 结构体相当于基于 objc_object 结构体的OC类对象的结构体。
// 对于其中成员变量 isa 进行初始化:
isa = &_NSConcreteStackBlock;
// 即 _NSConcreteStackBlock 相当于 class_t 结构体实例。
// 在将 Block 作为 OC 的对象处理时,关于该类的信息放置于 _NSConcreteStackBlock 中。
4、截获局部变量
“截获局部变量值”意味着在执行 Block 语法时,Block 语法表达式所使用的局部变量值被保存到 Block 的结构体实例中。
OC代码:
int mainDemo() {
int dmy = 256;
int val = 10;
const char *fmt = "var = %d\n";
void (^blk)(void) = ^{
printf(fmt, val);
};
val = 2;
fmt = "These values were changed. var = %d\n";
blk();
return 0;
}
转换后C++代码核心部分:
struct __mainDemo_block_impl_0 {
struct __block_impl impl;
struct __mainDemo_block_desc_0* Desc;
//3、block中未使用的局部变量不会被截获
//4、block中使用的局部变量,截获的是自动变量值
const char *fmt;
int val;
};
static void __mainDemo_block_func_0(struct __mainDemo_block_impl_0 *__cself) {
// “截获局部变量值”意味着在执行 Block 语法时
// Block 语法表达式所使用的局部变量值被保存到 Block 的结构体实例中
const char *fmt = __cself->fmt;
int val = __cself->val;
printf(fmt, val);
}
int mainDemo() {
int dmy = 256;
int val = 10;
const char *fmt = "var = %d\n";
struct __mainDemo_block_impl_0 tmp = __mainDemo_block_impl_0(__mainDemo_block_func_0,
&__mainDemo_block_desc_0_DATA, fmt, val);
struct __mainDemo_block_impl_0 *blk = &tmp;
val = 2;
fmt = "These values were changed. var = %d\n";
blk->FuncPtr(blk);
return 0;
}
5、解决 Block 中不能保存值的问题
C语言的函数中可能使用的变量:
- 局部变量
- 函数的参数
- 静态变量
- 静态全局变量
- 全局变量
其中,在函数的多次调用之间能够传递值的变量有:
- 静态变量
- 静态全局变量
- 全局变量
5.1 在函数的多次调用之间能够传递值的变量,在 Block 中也可以改写值
- 对 静态全局变量 和 全局变量 的访问于转换前完全相同
- 对 静态变量 是通过把静态变量的指针传递给Block结构体保存后,进行访问
OC代码:
int global_val = 1;
static int static_global_val = 2;
int mainDemo() {
static int static_val = 3;
void (^blk)(void) = ^{
global_val *= 1;
static_global_val *= 2;
static_val *= 3;
};
blk();
return 0;
}
转换成C++后:
int global_val = 1;
static int static_global_val = 2;
struct __mainDemo_block_impl_0 {
struct __block_impl impl;
struct __mainDemo_block_desc_0* Desc;
int *static_val;
__mainDemo_block_impl_0(void *fp, struct __mainDemo_block_desc_0 *desc,
*_static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//block函数部分
static void __mainDemo_block_func_0(struct __mainDemo_block_impl_0 *__cself) {
int *static_val = __cself->static_val;
global_val *= 1;
static_global_val *= 3;
(*static_val) *= 3;
}
static struct __mainDemo_block_desc_0 {
size_t reserved;
size_t Block_size;
} __mainDemo_block_desc_0_DATA = {
0,
sizeof(struct __mainDemo_block_impl_0),
};
int mainDemo() {
static int static_val = 3;
blk = &__mainDemo_block_impl_0(__mainDemo_block_func_0,
&__mainDemo_block_desc_0_DATA, &static_val);
return 0;
}
5.2 __block 存储域类说明符
OC代码:
int mainDemo() {
__block int block_val = 10;
void (^blk)(void) = ^{
block_val = 100;
};
blk();
return 0;
}
转换成C++后:
// __block类型结构体
struct __Block_byref_block_val_0 {
void *__isa;
__Block_byref_block_val_0 *__forwarding;//指向自己的指针
int __flags;
int __size;
int block_val;//通过__forwarding进行访问
};
struct __mainDemo_block_impl_0 {
struct __block_impl impl;
struct __mainDemo_block_desc_0* Desc;
__Block_byref_block_val_0 *block_val;
__mainDemo_block_impl_0(void *fp, struct __mainDemo_block_desc_0 *desc,
__Block_byref_block_val_0 *_block_val, int flags=0)
: block_val(_block_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __mainDemo_block_func_0(struct __mainDemo_block_impl_0 *__cself) {
__Block_byref_block_val_0 *block_val = __cself->block_val;
(block_val->__forwarding->block_val) = 100;
}
static void __mainDemo_block_copy_0(struct __mainDemo_block_impl_0*dst, struct __mainDemo_block_impl_0*src) {
_Block_object_assign((void*)&dst->block_val, (void*)src->block_val, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __mainDemo_block_dispose_0(struct __mainDemo_block_impl_0*src) {
_Block_object_dispose((void*)src->block_val, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __mainDemo_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __mainDemo_block_impl_0*, struct __mainDemo_block_impl_0*);
void (*dispose)(struct __mainDemo_block_impl_0*);
} __mainDemo_block_desc_0_DATA = {
0,
sizeof(struct __mainDemo_block_impl_0),
__mainDemo_block_copy_0,
__mainDemo_block_dispose_0
};
int mainDemo() {
__Block_byref_block_val_0 block_val = {
0,
&block_val,
0,
__Block_byref_block_val_0,
10
};
blk = __mainDemo_block_impl_0(
__mainDemo_block_func_0, &__mainDemo_block_desc_0_DATA, &val, 0x22000000);
return 0;
}
- __block 变量会变成栈上生成的 __Block_byref_block_val_0 结构体实例
- 该结构体持有原局部变量的成员变量
- __Block_byref_block_val_0 结构体实例的成员变量 __forwarding 持有指向该实例自身的指针
- 通过 __forwarding 成员变量访问成员变量val
- __block 变量的 __Block_byref_block_val_0 结构体并不在 Block 用 __mainDemo_block_impl_0 结构体中,这样做是为了在多个 Block 中使用 __block 变量
6、Block 存储域
- Block 会转换为 Block 的结构体类型的自动变量
- __block 变量转换为 __block 变量的结构体类型的自动变量
将 Block 当作 OC对象 来看时,Block 有如下种类:
- _NSConcreteStackBlock
- _NSConcreteGlobalBlock
- _NSConcreteMallocBlock
1、Block 何时为 _NSConcreteGlobalBlock 类对象:
- 在记述全局变量的地方使用 Block 语法时
- Block 语法的表达式中不实用应截获的自动变量时
2、设置在栈上的Block,如果其所属的变量作用域结束,该 Block 就被废弃,__block 变量也会被废弃:
3、 Blocks 提供了将 Block 和 __block 变量从栈上复制到堆上的方法来解决
4、各种 Block 调用 copy 方法的效果:
5、栈上 Block 复制到堆上的时机:
- 调用 Block 的 copy 实例方法时
- Block 作为函数返回值返回时
- 将 Block 赋值给附有 __strong 修饰符 id 类型的类或 Block 类型成员变量时
- 在 GCD 的API中传递Block时
7、__block 变量存储域
- 在任何一个 Block 从栈复制到堆时,__block 变量也会一并从栈复制到堆并被该 Block 所持有。当剩下的 Block 从栈复制到堆时,被复制的 Block 持有 __block 变量,并增加 __block 变量的引用计数。
- 如果配置在堆上的 Block 被废弃,那么它所使用的 __block 变量也就被释放。此思考方式与 OC 的引用计数式内存管理相同。
- __block 变量用结构体成员变量 __forwarding 的原因:
通过该功能,无论在 Block 语法位置,还是 __block 变量配置在栈上或堆上,都可以顺利访问同一个 __block 变量。