Blocks
2.1 blocks 概要
什么是blocks?
带有自动变量(局部变量)的匿名函数 [函数指针]
可分: (1)带有自动变量 (2)匿名函数
C 语言标准不允许存在这样不含有名称的函数。
C语言的函数中可能使用的变量;
- 自动变量(局部变量)
- 函数的参数
- 静态变量(静态局部变量)
- 静态全局变量
- 全局变量
其中,在函数的多次调用之间能够传递值的变量有;
(1)静态变量(静态局部变量) (2)静态全局变量 (3)全局变量
C++ 中的静态变量只可以复制一次的实现原理
blocks 提供了类似由C++和OC类生成
实例或对象来保持变量值的方法
,其代码量与编写C语言函数差不多。 block 保存自动变量值。
2.2 blocks模式
2.2.1 block 语法
^(int event) { // 省略
printf("button id event:%d",event);
}
^ void(int event) { // 全写法
printf("button id event:%d",event);
}
和C语言函数相比,只有两点不同
(1)没有函数名 【因为它是那你明天函数】
(2) 带有 ^ 【返回值类型带有 ^ 记号】
省略的样式
省略的样式
2.2.2 block 类型变量
int func(int count) {
return count + 1;
}
int (*funcptr)(int) = &func;
c语言函数中,可以将锁定义函数的地址赋值给函数指针类型变量中。 【所以,这里就是讲函数func的地址赋值给之后怎变量funcptr】
block类比
由于block方式极为复杂, 可以和函数指针类型一样,使用typedef来解决问题。
typedef 让记述方式更加简单
typedef int(^Blk)(int); 这样 就声明了一个Blk类型变量。
// 原来的记述方式
void func(int (^blk)(int))
// 现在的方式
void func(Blk blk)
将赋值 给Block类型变量中的block方法像C语言通常的函数调用那样使用。这种方法与使用函数指针累心变量调用函数的方法几乎完全相同。
函数指针类型变量:
int result = (*funcptr)(10);
// 调用block类型变量:
int result = blk(10);
//PS: 可以看到它们的调用没有什么区别。
// block 作为参数和其他变量作为参数没有什么区别。
int func(Blk blk, int rate) {return blk(rate); }
- (int)methodUsingBlock:(Blk)blk rate:(int)rate {return blk(rate);}
PS : block 完全可以和其他变量一样进行使用。
2.2.3 截获自动变量值
上面我们已经理解了“带有自动变量值的匿名函数” 中的匿名函数。
而“带有自动变量值” 是什么呢? 在blocks中表现为截获自动变量值。
{
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 ;
}
结果:val= 10;
也就是里面使用的是瞬间的值。即为捕获的是外面变量的值的瞬间值。
2.2.4 __block 说明符
获取自动变量值,即为在block里面是不可以修改外面的自动变量的值的。 只是拿来在block进行读取使用而已。
想要给外面的自动变量进行赋值的时候,需要使用__block 来进行修饰这个变量。
{
int dmy = 256;
__block 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;
}
使用了__block 修饰符之后。
结果: val = 2
PS: 使用附有__block 说明符的自动变量可在block中赋值,该变量称为 __block 变量。
2.2.5 截获的自动变量
直接给截获的自动变量赋值,将会报错。
如果截获OC对象,调用变更该对象的方法会产生编译错误吗?
NSMutableArray *array = [NSMutableArray array];
//没有报错
void (^blk)(void) =^(void) {
id obj = [NSObject new];
[array addObject:obj];
};
// 报错
void (^blk1)(void) = ^(void) {
array = [NSMutableArray new];
};
调用array的有关方法是没有编译错误的,而直接给array赋值就会产生错误的。 该源代码中截获的变量值为NSMutableArray类的的对象。 如果用C语言来描述,即为截获NSMutableArray类对象用的结构体实例指针。 虽然赋值给截获的自动变量array的操作会产生编译错误,但是使用截获的值却不会有任何问题。 如果要进行赋值,还是要使用__block 进行修饰。
const char text[] ="hello";
void (^blk)(void) = ^(void) {
printf("%c \n", text[2]);
};
使用C语言数组时候必须要小心使用其指针。
上面只是使用C语言的字符串字面量数组, 而并没有向截获自动变量赋值,因此看似灭有问题。实际会产生编译错误。
AN : 因为现在blocks中,截获自动变量的方法并没有实现对C语言数据的截获。 这个时候就需要使用只恨来解决这个问题。
const char *text ="hello";
void (^blk)(void) = ^(void) {
printf("%c \n", text[2]);
};
2.3 Blocks 的实现
clang -rewrite-objc 源代码文件名 将OC的源码编译成为C++的源码查看。
// 一个简单使用block的代码
- (void)initTest {
void (^blk)(void) = ^(void){
printf("block \n");
};
blk();
return;
}
// 上面的方法clang之后变成了C语言的代码
struct __block_impl { //block实现的内部结构
void *isa;
int Flags;
int Reserved;
void *FuncPtr;// 用于保存函数地址
};
// block对象的结构
struct __XNBlockModel__initTest_block_impl_0 {
struct __block_impl impl; // 声明了这样的实现变量 , 看上面的结构
struct __XNBlockModel__initTest_block_desc_0* Desc; //block的大小
// 给imp赋值有关的值
// 给Desc赋值有感的值
__XNBlockModel__initTest_block_impl_0(void *fp, struct __XNBlockModel__initTest_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp; // 调用的函数
Desc = desc; // 描述内容
}
};
// block里面的内容
static void __XNBlockModel__initTest_block_func_0(struct __XNBlockModel__initTest_block_impl_0 *__cself) {
printf("block \n");
}
// 这个只是表示大小
static struct __XNBlockModel__initTest_block_desc_0 {
size_t reserved;
size_t Block_size; // block大小
} __XNBlockModel__initTest_block_desc_0_DATA = { 0, sizeof(struct __XNBlockModel__initTest_block_impl_0)}; // 这里初始化了desc内容
// initTest方法
static void _I_XNBlockModel_initTest(XNBlockModel * self, SEL _cmd) {
void (*blk)(void) = ((void (*)())&__XNBlockModel__initTest_block_impl_0((void *)__XNBlockModel__initTest_block_func_0, &__XNBlockModel__initTest_block_desc_0_DATA));
// void (*blk)(void) = &__XNBlockModel__initTest_block_impl_0(__XNBlockModel__initTest_block_func_0, &__XNBlockModel__initTest_block_desc_0_DATA)
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
// 简化:(*blk ->FuncPtr)(blk)
return;
}
// 该函数里面的__cself 相当于C++ 中的this 。 即参数__cself 为指向block值的变量。
// 上面代码描述: __XNBlockModel__initTest_block_impl_0 结构体类型的自动变量【即栈上生成对应结构体实例指针】, 赋值给__XNBlockModel__initTest_block_impl_0 结构体指针类型的白能量blk。
//(*blk ->FuncPtr)(blk) 函数指针调用函数。
isa =&_NSConcreteStackBlock 是什么呢?
这个内容就是,我们获取_NSConcreteStackBlock 类对象的地址赋值给了当前block对象的isa, 说明isa的有关内容可以在_NSConcreteStackBlock , 可以说:
_NSConcreteStackBlock 是__XNBlockModel__initTest_block_impl_0 的元类。
即为:在将block作为OC的对象处理时,关于该类的信息放置于_NSConcreteStackBlock中。
2.3.2 截获自动变量值
// OC 中的代码
- (void)initTest {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d \n";
void (^blk)(void) = ^(void) {
printf(fmt, val);
};
val = 2;
fmt = "These values were changed. val = %d \n";
blk();
return;
}
clang 转成C代码之后
// 和上面的区别是:多了fmt 、val
// Note: Block 语法表达式中没有使用的自动变量不会被追加, eg:dmy
// Block的自动变量截获只针对Block中使用的自动变量。
struct __XNBlockModel__initTest_block_impl_0 {
struct __block_impl impl;
struct __XNBlockModel__initTest_block_desc_0* Desc;
const char *fmt;
int val;
// 构造函数, 多了fmt 和val的初始化
__XNBlockModel__initTest_block_impl_0(void *fp, struct __XNBlockModel__initTest_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 __XNBlockModel__initTest_block_func_0(struct __XNBlockModel__initTest_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy (范围拷贝)
int val = __cself->val; // bound by copy
printf(fmt, val); // 打印出来这个内容
}
static struct __XNBlockModel__initTest_block_desc_0 {
size_t reserved;
size_t Block_size;
} __XNBlockModel__initTest_block_desc_0_DATA = { 0, sizeof(struct __XNBlockModel__initTest_block_impl_0)};
static void _I_XNBlockModel_initTest(XNBlockModel * self, SEL _cmd) {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d \n";
void (*blk)(void) = ((void (*)())&__XNBlockModel__initTest_block_impl_0((void *)__XNBlockModel__initTest_block_func_0, &__XNBlockModel__initTest_block_desc_0_DATA, fmt, val));
// 看到这里,fmt, val是传值进去的
// 简化:void (*blk)(void) = &__XNBlockModel__initTest_block_impl_0(__XNBlockModel__initTest_block_func_0, &__XNBlockModel__initTest_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;
}
// Note: “截获自动变量值” : 在执行block语法时候,block语法表达式使用的自动变量值被保存到Block的结构体实例中。
C语言是不可以编译的
C语言是不可以编译的,xcode上的报错
2.3.3 __block 说明符
block获取的是值, 即为值传递, 所以,即使可以在block中赋值给val这个,也是不会改变原来传递的值的。
给val赋值,编译出现错误
这样:我们就无法在Block中保存至了,极为不便。
解决方案:
1) C语言中有一个变量,允许block改写 值。
静态变量、静态全局变量、全局变量
block语法的匿名函数部分简单地变换为了C语言函数, 但从这个变换的函数中访问静态全局变量、全局变量并没有任何改变,可以直接使用。
但是,静态变量情况下,转换后的函数原本就设置为含有block语法的函数外,所以无法从变量作用域访问。
// OC 源码
int global_val = 1;
static int static_global_val = 2;
- (void)initTest {
static int static_val = 3;
void (^blk)(void) = ^(void) {
global_val *=1;
static_global_val *=2;
static_val *=3;
};
blk();
}
// 静态变量转化为C语言之后
int global_val = 1;
static int static_global_val = 2;
struct __XNBlockModel__initTest_block_impl_0 {
struct __block_impl impl;
struct __XNBlockModel__initTest_block_desc_0* Desc;
int *static_val; // 静态变量
__XNBlockModel__initTest_block_impl_0(void *fp, struct __XNBlockModel__initTest_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 __XNBlockModel__initTest_block_func_0(struct __XNBlockModel__initTest_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *=1;
static_global_val *=2;
(*static_val) *=3; // 静态变量的访问
}
static struct __XNBlockModel__initTest_block_desc_0 {
size_t reserved;
size_t Block_size;
} __XNBlockModel__initTest_block_desc_0_DATA = { 0, sizeof(struct __XNBlockModel__initTest_block_impl_0)};
static void _I_XNBlockModel_initTest(XNBlockModel * self, SEL _cmd) {
static int static_val = 3;
void (*blk)(void) = ((void (*)())&__XNBlockModel__initTest_block_impl_0((void *)__XNBlockModel__initTest_block_func_0, &__XNBlockModel__initTest_block_desc_0_DATA, &static_val));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
没有使用静态变量的原因
2) 方法2 使用__block 说明符。更加准确说法:__block 存储域类说明符
C语言有存储域类说明符: typedef ,extern, static, auto, register
__block 类似 static, auto, register , 用于指定将变量值设置到哪个存储域中。
// OC源代码
- (void)initTest {
__block int val = 10;
void (^blk)(void) = ^(void) {
val = 1;
};
blk();
}
转成为C的源码:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __XNBlockModel__initTest_block_impl_0 {
struct __block_impl impl;
struct __XNBlockModel__initTest_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref 持有__Block_byref_val_0 类型的实例变量
__XNBlockModel__initTest_block_impl_0(void *fp, struct __XNBlockModel__initTest_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 __XNBlockModel__initTest_block_func_0(struct __XNBlockModel__initTest_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
// 比原来多了下面连个方法,copy0 , dispose_0
static void __XNBlockModel__initTest_block_copy_0(struct __XNBlockModel__initTest_block_impl_0*dst, struct __XNBlockModel__initTest_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __XNBlockModel__initTest_block_dispose_0(struct __XNBlockModel__initTest_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __XNBlockModel__initTest_block_desc_0 {
size_t reserved;
size_t Block_size;
// 多了这两个函数指针
void (*copy)(struct __XNBlockModel__initTest_block_impl_0*, struct __XNBlockModel__initTest_block_impl_0*);
void (*dispose)(struct __XNBlockModel__initTest_block_impl_0*);
} __XNBlockModel__initTest_block_desc_0_DATA = { 0, sizeof(struct __XNBlockModel__initTest_block_impl_0), __XNBlockModel__initTest_block_copy_0, __XNBlockModel__initTest_block_dispose_0};
static void _I_XNBlockModel_initTest(XNBlockModel * self, SEL _cmd) {
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
// __Block_byref_val_0 val = {0, &val, 0, sizeof(__Block_byref_val_0), 10};
void (*blk)(void) = ((void (*)())&__XNBlockModel__initTest_block_impl_0((void *)__XNBlockModel__initTest_block_func_0, &__XNBlockModel__initTest_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
// void (*blk)(void) = &__XNBlockModel__initTest_block_impl_0(__XNBlockModel__initTest_block_func_0, &__XNBlockModel__initTest_block_desc_0_DATA, &val, 570425344));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
// (*blk -> FuncPtr)(blk)
}
// Note
// (1)__block 变量也同Block一样变成了__Block_byref_val_0 结构体类型的自动变量。【即 栈上生成的__Block_byref_val_0结构体实例】 ,该比那拉给你初始化为0, 切这个值也出现在结构体实例化的初始化中。 这以为这该结构体持有原自动变量
为什么使用一个__forwarding成员变量
下面主要说明:
1)Block超出变量作用域可存在的理由
2)__block 变量的结构体成员变量__forwarding存在的理由。
2.3.4 Block 存储域
forward的原因
2.3.5 __block 变量存储域
【需要补充】
block上面的内容, 这个需要进一步去理解。 block等等内容。