Block与对象
首先我们先反思几个问题:
block到底是不是对象?
如果是对象,和某个已定义的类的实例对象在使用上是不是一样的?
如果不一样,主要的区别是什么?
深入理解Objective-C的Block这篇文章做了很好的讲解。
弄清Block的存储域
直接上代码:
typedef void (^blk)(void);
blk global_blk = ^{};
int main() {
int num = 5;
blk local_global_blk = ^{NSLog(@"no use auto variable");};
blk local_malloc_blk = ^{NSLog(@"use auto variable:%i", num);};
NSArray *blks = [[NSArray alloc] initWithObjects:
global_blk,
local_global_blk,
local_malloc_blk,
^{ NSLog(@"this is block 1:%i", num); },
[^{ NSLog(@"this is block 2:%i", num); } copy],
nil];
NSLog(@"%@", blks);
return 0;
}
打印结果:
**2016-12-23 10:22:31.917 IOSAPILearn[11561:33792561] (**
** "<__NSGlobalBlock__: 0x1044d4bb0>",**
** "<__NSGlobalBlock__: 0x1044d4bf0>",**
** "<__NSMallocBlock__: 0x60000005bc30>",**
** "<__NSStackBlock__: 0x7fff5b757598>",**
** "<__NSMallocBlock__: 0x60000005be40>"**
**)**
由此看出Block的存储域有三种:
- 数据区域.data区:_NSConcreteClobalBlock
- 堆:_NSConcreteMallocBlock
- 栈:_NSConcreteStackBlock
注意:Block对象与一般的类实例对象有所不同,一个主要的区别就是分配的位置不同,block默认在栈上分配,一般类的实例对象在堆上分配。
什么时候分配在数据域.data区?
- 记述全局变量的地方有Block语法时。
- Block语法的表达式中不截获自动变量时。
什么时候分配在栈?
除此之外Block语法生成的Block为_NSConcreteStackBlock类对象实例,且设置在栈上。
什么时候将栈上的Block复制到堆上呢?
- 调用Block的copy实例方法。
- 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时。
- Block作为函数返回值返回时,编译器会自动生成复制到堆中的代码。
- Cocoa框架的方法且方法名中含有usingBlock等时。
- Grand Central Dispatch的API
注意:retain是对一个在堆中分配内存的对象的引用计数做了增加,执行release操作的时候检查计数是否为1,如果是则释放堆中内存。对于Global的Block,我们无需多处理,不需retain和copy,因为即使你这样做了,似乎也不会有什么两样。对于Stack的Block,如果不做任何操作,随栈帧自生自灭。而如果想让它活得比stack 帧更久,那就调用Block_copy(),让它搬家到堆内存上。而对于已经在堆上的block,也不要指望通过copy进行“真正的copy”,因为其引用到的变量仍然会是同一份,在这个意义上看,这里的copy和retain的作用已经非常类似。
按配置Blcok的存储域,将copy方法进行复制进行的动作总结如下:
Block的类 | 副本源的配置存储域 | 复制结果 |
---|---|---|
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteGlobalBlock | 程序的数据区域 | 什么也不做 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |
弄清Block如何实现截获自动变量值
Blocks中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值,因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使改写了Block中使用的自动变量的值也不会影响Block执行时自动变量的值。
看代码:
static int staic_global_variable = 10;
int global_variable = 12;
- (void)captureAutomaticVariable {
int local_variable = 5;
static int local_static_variable = 13;
blk local_malloc_blk = ^{
NSLog(@"use local_variable: %i", local_variable);
NSLog(@"use local_static_variable: %i", local_static_variable);
NSLog(@"use staic_global_variable: %i", staic_global_variable);
NSLog(@"use global_variable: %i", global_variable);
};
local_variable = 0;
local_static_variable = 0;
staic_global_variable = 0;
global_variable = 0;
local_malloc_blk();
}
打印结果:
**2016-12-23 11:15:34.279 IOSAPILearn[13578:33912186] use local_variable: 5**
**2016-12-23 11:15:34.280 IOSAPILearn[13578:33912186] use local_static_variable: 0**
**2016-12-23 11:15:34.280 IOSAPILearn[13578:33912186] use staic_global_variable: 0**
**2016-12-23 11:15:34.280 IOSAPILearn[13578:33912186] use global_variable: 0**
可以看出以下三种变量超出与变量作用域的存在:
- 静态变量 (传入地址实现)
- 全局静态变量
- 全局变量
使用$ clang -rewrite-objc -fobjc-arc main.m 看运行时实现方式:
注意:要进入main.m的目录下执行,才会看到main.cpp文件
关键代码:
typedef void (*blk)(void);
static int staic_global_variable = 10;
int global_variable = 12;
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;
int local_variable;
int *local_static_variable;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _local_variable, int *_local_static_variable, int flags=0) : local_variable(_local_variable), local_static_variable(_local_static_variable) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int local_variable = __cself->local_variable; // bound by copy
int *local_static_variable = __cself->local_static_variable; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_0, local_variable);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_1, (*local_static_variable));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_2, staic_global_variable);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_3, global_variable);
}
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 local_variable = 5;
static int local_static_variable = 13;
blk local_malloc_blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, local_variable, &local_static_variable));
local_variable = 0;
local_static_variable = 0;
staic_global_variable = 0;
global_variable = 0;
((void (*)(__block_impl *))((__block_impl *)local_malloc_blk)->FuncPtr)((__block_impl *)local_malloc_blk);
return 0;
}
分析:
转换前的代码:
int local_variable = 5;
static int local_static_variable = 13;
blk local_malloc_blk = ^{
NSLog(@"use local_variable: %i", local_variable);
NSLog(@"use local_static_variable: %i", local_static_variable);
NSLog(@"use staic_global_variable: %i", staic_global_variable);
NSLog(@"use global_variable: %i", global_variable);
};
转换后的代码:
int local_variable = 5;
static int local_static_variable = 13;
blk local_malloc_blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, local_variable, &local_static_variable));
为看的更清楚些,把强制转换去掉:
blk local_malloc_blk = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, local_variable, &local_static_variable)
可以看出local_malloc_blk是一个__main_block_impl_0结构体,__main_block_impl_0结构体使用__main_block_func_0,__main_block_desc_0_DATA,local_variable,&local_static_variable初始化,注意local_variable和&local_static_variable,一个传入值,一个传入地址。
下面来看看__main_block_impl_0的构成:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int local_variable;
int *local_static_variable;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _local_variable, int *_local_static_variable, int flags=0) : local_variable(_local_variable), local_static_variable(_local_static_variable) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
可以看出__main_block_impl_0是一个类(有isa),它的成员变量impl中存储了一个FuncPtr(函数指针),从上面的初始化代码中可以看出__main_block_impl_0使用__main_block_func_0函数指针来初始化的。
__main_block_func_0的代码如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int local_variable = __cself->local_variable; // bound by copy
int *local_static_variable = __cself->local_static_variable; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_0, local_variable);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_1, (*local_static_variable));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_2, staic_global_variable);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_c51446_mi_3, global_variable);
}
是由local_malloc_blk闭包体转换过来的:
blk local_malloc_blk = ^{
NSLog(@"use local_variable: %i", local_variable);
NSLog(@"use local_static_variable: %i", local_static_variable);
NSLog(@"use staic_global_variable: %i", staic_global_variable);
NSLog(@"use global_variable: %i", global_variable);
};
由上可知,闭包为一个__main_block_impl_0对象,闭包体转换为了一个__main_block_func_0函数,其中__main_block_func_0这个函数需要传入struct __main_block_impl_0 *__cself
为参数。这就有点像我们平时函数体中的self了,__main_block_func_0使用__cself可以获取__main_block_impl_0中捕获到的自动变量local_variable和local_static_variable,从而执行对应的代码。
总结:
static int staic_global_variable = 10;
int global_variable = 12;
转换为运行时代码后并没有什么改变,__main_block_func_0中仍然按全局静态变量和全局变量使用,所以它们超出与变量作用域而存在。
int local_variable = 5;
static int local_static_variable = 13;
被__main_block_impl_0捕获为了自己的变量,但是local_variable是传入值进去,而&local_static_variable是传入地址进去,在__main_block_func_0中使用时,用*local_static_variable来取值,所以,local_static_variable可以超出变量作用域而存在。
使用__block修饰:
__block修饰符,更准确的表达方式为“__block存储域说明符(__block storage-class-specifier)”。用于指定Block中想变更值的自动变量。
代码如下:
- (void)useBlockSpecifier {
__block int num = 5;
blk captureBlk = ^{
num += 1;
NSLog(@"num is = %i", num);
};
num = 1;
captureBlk();
captureBlk();
NSLog(@"current num is = %i", num);
}
打印结果:
**2016-12-23 14:06:20.957 IOSAPILearn[19064:34269365] block num is = 2**
**2016-12-23 14:06:20.959 IOSAPILearn[19064:34269365] block num is = 3**
**2016-12-23 14:06:20.959 IOSAPILearn[19064:34269365] current num is = 3**
可以看出通过__block修饰的变量在闭包体中可以修改,闭包体内与闭包体外的num是同一个值,那它是如何做到的呢?
使用$ clang -rewrite-objc -fobjc-arc main.m 看运行时实现方式:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __Block_byref_num_0 {
void *__isa;
__Block_byref_num_0 *__forwarding;
int __flags;
int __size;
int num;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_num_0 *num; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__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_num_0 *num = __cself->num; // bound by ref
(num->__forwarding->num) += 1;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_ef88c5_mi_0, (num->__forwarding->num));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->num, 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_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 5};
blk captureBlk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
(num.__forwarding->num) = 1;
((void (*)(__block_impl *))((__block_impl *)captureBlk)->FuncPtr)((__block_impl *)captureBlk);
((void (*)(__block_impl *))((__block_impl *)captureBlk)->FuncPtr)((__block_impl *)captureBlk);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_91_s7045gc12855hsgsr86xhx880000gn_T_main_ef88c5_mi_1, (num.__forwarding->num));
return 0;
}
这里注意到__main_block_impl_0中对捕获的num是通过对象__Block_byref_num_0 *num //by ref
来存储的。
__Block_byref_num_0定义如下:
struct __Block_byref_num_0 {
void *__isa;
__Block_byref_num_0 *__forwarding;
int __flags;
int __size;
int num; //用来保存num值
};
用__block修饰的num变量转化成了以下形式:
__attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 5};
即创建一个__Block_byref_num_0对象num,使__forwarding指针指向栈/堆中的内存。
- 当block在栈中时,__forwarding指向栈的。
- 当block在堆中时,__forwarding指向堆的。
__forwarding用处:
- 无论是在栈中还是在堆中的num使用__forwarding指向的都是同一个地址。
- 所以可以在block体中修改变量值,也可以在block上下文中修改变量值,他们修改的是同一个值。
__main_block_copy_0和__main_block_dispose_0函数:
__main_block_copy_0是Block从栈上复制在堆上的时候调用
__main_block_dispose_0是Block从堆上销毁的时候调用
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
_Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);
}
__Block_object_assign函数调用相当于retain实例方法的函数,将对象赋值在对象类型的结构体成员变量中。
__Block_object_dispose函数,释放对象类型的结构体的成员变量。
Objective-C的运行时库能够准确的把握Block从栈复制到堆以及堆上的Block被废弃的时机,因此Block结构体中即使含有附有__strong修饰符或者__weak修饰符的变量也可以恰当的进行初始化和废弃。为此需要在结构体__main_block_desc_0中增加成员变量copy和dispose,以及作为指针赋值给该成员变量的__main_block_copy_0函数和__main_block_dispose_0函数。