参考:Objective-C高级编程 iOS与OS X多线程和内存管理
1.1 Blocks概要
1.1.1 什么是Blocks
-
Blocks
是C
语言的扩充功能,用一句话表示带有自动变量(局部变量)的匿名函数
-
Blocks
中将匿名函数部分称为Block literal
,或简称为Block
1.2 Blocks模式
1.2.1 Block语法
不在本文讨论范围内,不做详细说明
1.2.2 Block类型变量
声明Block
类型变量的示例如下: int (^blk)(int);
该Block类型变量与一般的C语言变量完全相同,可以作为以下用途
> 自动变量
> 函数参数
> 静态变量
> 静态全局变量
> 全局变量
typedef int (^blk_t)(int);
通过typedef
可声明blk_t
类型变量
1.2.3 截获自动变量值
带有自动变量值
在Blocks中表现为截获自动变量值
int main()
{
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;
}
打印结果:val = 10
Blocks
中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。所以执行Block语法后,即使改变了Block中使用的自动变量的值也不会影响到Block执行时自动变量的值.
1.2.4 _ _block说明符
若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在该自动变量上附加__block说明符
1.2.5 截获的自动变量
int main()
{
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
id obj = [[NSObject alloc] init];
[array addObject:obj];
};
return 0;
}
这是没有问题的,而面向截获的array赋值则会产生编译错误。虽然赋值给截获的自动变量array的操作会产生编译错误,但使用截获的值却不会有任何问题。
另外,在使用C语言数组时必须小心使用其指针,源代码示例如下:
int main()
{
const char text[] = "hello";
void(^blk)(void) = ^{printf("%c\n",text[2]);};
return 0;
}
只是使用C语言的字符串字面量数组,而并没有想截获的自动变量赋值,因此看似没有问题,但是实际上产生了编译错误。原因在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获
,这时,使用指针就可以解决问题:
int main()
{
const char *text = "hello";
void(^blk)(void) = ^{printf("%c\n",text[2]);};
blk();
return 0;
}
1.3 Blocks的实现
1.3.1 Block的实质
我们首先通过clang(LLVM编译器)
将含有Block语法的源代码变换成C++
的源代码(说是C++,其实也仅是使用了Struct结构,其本质是C语言源代码)。
转换语句clang -rewrite-objc 源代码文件名
下面我们转换Block语法
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) {
//_NSConcreteStackBlock用于初始化_block_impl结构体的isa成员
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()
{
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语法。
^{printf("%c\n",text[2]);};
可以看到,变换后的源代码中也含有相同的表达式
//参数__cself为指向Block值的变量(类似于Objective-C实例方法中指向对象自身的变量self)
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
printf("Block\n");
}
如变换后的源代码所示,通过Blocks使用的匿名函数
实际上被作为简单的C语言函数
来处理.另外,根据Block语法所属的函数名(此处为main)和该Block语法在该函数出现的顺序值(此处为0)来给经clang变换的函数命名。
这里可能要进行补充
先来看看构造函数的调用
void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
因为转换较多,看起来不是很清楚,所以我们去掉转换的部分,具体如下:
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
以下为这部分代码最初的源代码
void(^blk)(void) = ^{printf("Block\n");};
我们来确认一下使用该Block的部分
blk()
这部分可变换为以下源代码
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
去掉转换部分
//static void __main_block_func_0(struct __main_block_impl_0 *__cself) 实际上是使用这个函数的指针调用函数
(*blk -> impl.FuncPtr)(blk);
这就是简单的使用函数指针调用函数
。由Block
语法转换的__main_block_func_0
函数的指针被赋值到成员变量FuncPtr
中。另外也说明了,__main_block_func_0
函数的参数__cself
指向Block
值。在调用该函数的源代码中可以看出Block
正是作为参数进行了传递。
下面对_NSConcreteStackBlock进行说明
//将Block指针赋给Block的结构体成员变量isa
impl.isa = &_NSConcreteStackBlock;
其实,所谓Block就是Objective-C对象。
id
这一变量类型用于存储Objective-C
对象。在Objective-C
源代码中,虽然可以向使用Void *
类型那样随意的使用id
,但此id
类型也能够在C语言中声明。在/usr/include/objc/runtime.h
中是如下声明的
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
};
///Class为objc_class结构体的指针类型
typedef struct objc_class *Class;
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// id为objc_object结构体的指针类型
typedef struct objc_object *id;
Objective-C中由类生成对象
意味着,像结构体这样生成由该类生成的对象的结构体实例
。生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量isa保持该类的结构体实例指针。
_NSConcreteStackBlock相当于class_t结构体实例
1.3.2 截获自动变量值
将截获自动变量值的源代码通过clang进行转换
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 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;
}
首先我们注意到,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)
构造函数调用结果如下
{
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
fmt = "val = %d\n";
val = 10;
}
由此可知,在__main_block_impl_0
结构体实例(Block)中,自动变量值被截获。
下面来看一下使用Block的匿名函数的实现
^{printf(fmt,val);}
该源代码可转换为以下函数
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);
}
总得来说,所谓截获自动变量
意味着在执行Block语法时,Block语法表达式所使用的自动变量值被保存到Block的结构体实例(即Block自身)中。
1.3.3 _ _block说明符
int main()
{
__block int val = 10;
void(^blk)(void) = ^{val = 1;};
return 0;
}
clang转换代码后
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;
}
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()
{
__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));
return 0;
}
其中__block int val = 10;
转换为结构体实例,即栈上生成的__Block_byref_val_0结构体实例
__Block_byref_val_0 val = {
(void*)0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
10
};
而且这个值出现在结构体实例的初始化中,这意味着该结构体持有相当于原自动变量的成员变量
。该结构体声明如下:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
下面这段给__Block变量赋值的代码^{val = 1;}
该源码转换如下:
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;
}
Block
的__main_block_impl_0
结构体实例持有指向__block
变量的__Block_byref_val_0
结构体实例指针。__Block_byref_val_0
结构体实例的成员变量__forwarding
持有指向该实例自身的指针。通过成员变量__forwarding
访问成员变量val
.
下面主要说明两个部分
1.Block
超出变量作用域可存在的理由
2.__block
变量的结构体成员变量_forwarding
存在的理由
有时在__block
变量配置在堆上的状态下,也可以访问栈上的__block
变量。在此情形下,只要栈上的结构体实例成员变量__forwarding
指向堆上的结构体实例,就可以正确进行访问。
1.3.4 Block存储域
名称 | 实质 |
---|---|
Block | 栈上Block的结构体实例 |
__block变量 | 栈上__block的结构体实例 |
将Block当作Objective-C对象来看时,该Block的类为_NSConcreteStackBlock
类 | 设置对象的存储域 |
---|---|
_NSConcreteStackBlock | 栈 |
_NSConcreteGlobalBlock | 程序的数据区域(.data区) |
_NSConcreteMallocBlock | 堆 |
只在截获自动变量时,Block用结构体实例截获的值才会根据执行时的状态变化
向方法或函数的参数中传递Block时
需要手动复制Block,但以下方法或函数不需要
* Cocoa框架的方法名中含有usingBlock等时
* GCD的API
将Block从栈上复制到堆上是相当消耗CPU的,因此只在此情形下让编程人员手动进行复制。
Block 的副本
Block 的类 | 副本源的配置存储域 | 复制效果 |
---|---|---|
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteGlobalBlock | 程序的数据区域(.data区) | 什么也不做 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |
1.3.5 _ _block变量存储域
Block
从栈复制到堆时对__block
变量产生的影响
__block变量的配置存储域 | Block从栈复制到堆时的影响 |
---|---|
栈 | 从栈复制到堆并被Block持有 |
堆 | 被Block持有 |
int main()
{
__block int val = 10;
//这个val使用的是复制到堆上的__block变量
void(^blk)(void) = [^{++val;} copy];
//为复制前栈上的__block变量用的结构体实例,但__forwarding 指向目标堆上的__block变量用结构体实例的地址
++val;
blk();
NSLog(@"%d",val);
return 0;
}
1.3.6 截获对象
除了以下情形外,推荐使用Block
的copy
实例方法
- Block 作为函数返回值返回时
- 将Block赋值给类的附有__strong修饰符的id类型或Block类型成员变量时
- 向方法名中含有usingBlock的cocoa框架方法或GCD的API中传递Block时
网友评论