赞
踩
Blocks是c语言的扩展,block是一个带有自动变量值的匿名函数,它也是一个数据类型;
或者说:带有自动变量的匿名函数;
语法类型如下:
^ 返回值类型 参数类型 表达式
void (^block1)()//无返回值,无参数
int (^block2)(int num1,int num2)//int类型返回值i,两个int类型参数
语法中要注意的几个点:
^void (void) {printf("Blocks\n");
这里强调了blocks可以是一种类型变量,后面我们还要学到blocks类型的对象 ;
声明Blocks类型变量的实例如下:
int (^block) (int) ;
Blocks类型变量与一般的c语言变量完全相同 ;可作为以下用于使用
因为和通常的变量相同,所以当然可以由Blockleixing变量向Block类型变量赋值 ;
int (^blk1) (int) = blk ;
int (^blk2) (int) ;
blk2 = blk1 ;
可以作为函数参数传递,也可以作为函数返回值返回:
void func (int (^blk) (int) ) {
int (^func () ) (int ) {
return ^ (int count ) {return count + 1;}//不过这个函数没太看懂,有点奇怪
上面这几种计述方式非常复杂,应为blocks是一种数据类型,所以我们可以使用typedef解决该问题:
typedef int (^blk_t ) (int) ;
上面的例子可改为:
void func (blk_t blk) {
blk_t func () {
同样的:可以通过Block类型变量调用Block和使用Block的指针类型变量 ;
其实可以总结为;Block类型变量可像c语言中其他类型变量一样使用
在Blocks中,Blocks可以截获表达式中那个使用的变量,即保存该变量的瞬间值。随意即使之后改变原来的自动变量,也不会改变Blocks所截获的自动变量的值 ;
这一概念的实质是Blocks中的变量会将截获的值存进一个结构体对象中 ;
int main () {
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 0 ;
val = 10 ;
自动变量截获只能保存执行Block语法瞬间的值,保存后就不能修改其值,如果我们尝试修改其值 ;
int val = 0 ;
void (^blk) (void) = ^ (val = 1 ;) ;
blk ();
printf ("val = %d\n",val) ;
编译器此时会报错:
Block是“带有自动变量值的匿名函数:,但它实际上作为简单的c语言源代码处理的,通过支持Block的编译器,含有Block语法源代码转换为c语言源代码被编译 ;
int main () {
void (^blk) (void) = ^ {printf("Block\n";};
blk () ;
return 0 ;
可以转化为一下的代码:
交换后的源代码中:
static void _main_Block-func_0 (struct__main_block_imp1_0 * _cself) {
printf ("Block\n") ;
}
这里根据Block语法所属的函数名和该Block语法在该函数出现的顺序值来给转换后的函数命名 ;
参数_cself 是——main_block_impl_0结构体的指针,
struct _main_block_impl_0 {
struct _block_impl impl ;
struct _main_block_desc_0 * Desc ;
}
struct _block_impl {
void* isa ;
int Flags ;
int Reserved ;
void* FuncPtr ;
} ;
struct _main_Block_desc_0 {
unsigned long reserved ;
unsigned long Block_size ;
};
从上面这些结构体来看,Block本身的实现就是依赖于这些结构体对象,这也能更好的理解之后入变量截获的实现等 ;
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"); }
struct_main_block_impl_0 tmp = _main_block_impl_0 (_main_block_func_0, &_main_block_desc_0_DATA) ;
第一个参数是由Blcok语法转化的c语言函数指针,第二个参数是作为静态全局变量初始化的_main_block_desc_0 的结构体实例指针 ;这里意味着将Block语法生成的Block赋给Block类型变量blk ;
blk () ;
//变换后并去掉转换部分
(*blk->impl.FuncPtr) (blk) ;
这里_main_block_func_0 函数的指针被赋值成员变量FuncPtr中 ;
也说明了参数_cself指向Block值 ;
对于isa = &_NSConcreteStackBlock ;
这里要提到前面说过的;所谓Block就是oc对象 ;
上面所使用的objc_objectj结构体和objc_objc_class 结构体是在各对象和类的实现中使用的最基本的结构体 ;
对于下面
@interface Myobject : NSObject {
int val0 ;
int val1 ;
}
end
该类对象的结构体如下:
struct Myobject {
Class isa ;
int val0 ;
int val1 ;
} ;
即同过isa指针保持该类结构体实例指针 ;
这是再看上面的代码,_NSConcreteStackBlock相当于class_t结构体实例 ;在讲Block作为oc对象处理时,关于该类信息放置于_NSConcreteStackBlock中 ;
Block结构体:
书上的源码:
从上面的源码中,我们可以看到Block语法表达式中使用的自动变量被作为成员变量追加到咯_main_block_impl_0 结构体中 ;
struct _main_block_impl_0 {
struct _blcok_impl impl ;
struct _main_block_desc_0* Desc ;
const char* fmt ;
int val ;
} ;
Blcoks的自动变量截获只针对Block中使用的自动变量 ;
初始化该结构体的构造函数如下:
_main_block_impl_结构体实例的初始化如下:
也就是说 自动变量是在结构体中的成员变量中被截获保存其值 ;
总的来说,所谓“截获自动变量值”意味着在执行Block语法时,Block语法表达式所使用的自动变量值被保存到Block的结构体实例中 ;
对于之前说过的Blcok不能直接使用c语言数组类型的自动变量,可以看看下面构造函数:
void func (char a[10]) {
char b [10] = a ;
printf ("%d\n",b[0]) ;
}
int main () {
char a[10] = {2} ;
func (a) ;
}
将c语言数组类型变量赋值给c语言数组类型变量中
,这在c里时不允许的 ;
由上面可知,Block中所截获的自动变量仅仅截获自动变量的值。在Block结构体实例中重写该自动变量也不会改变原先可活的自动变量 ;
如果尝试修改就会引发编译错误 ;
但c语言中的
int global_val = 1 ;
static int static_global_val = 2 ;
int main () {
static int static_val = 2 ;
void (^blk) (void) = ^ {
global_val * = 1 ;
static_global_val* = 2 ;
static_val *= 3 ;
};
return 0 ;
}
转换后源码如下
主要注意一下静态变量static_val的转换 ;
在结构体得1尘缘变量中保存的是static_val的指针 ;
除了上面的三种变量,还可以使用__block说明符,也可以说是__block存储类说明符,
c语言中的存储域类说明符 :
__block说明符类似于static,auto,register说明符,用于指定将变量值设置到哪个存储域中。
使用了__block说明符后:
__block int val = 10 ;
这时,————block变量同Block一样变成__block_byref_val_0的结构体类型变量,也就是放在栈上的结构体实例,这个结构体初始化持有原自动变量的成员变量 ;
该结构体的声明如下:
struct __block_byref_val_0 {
void* __isa ;
__block_byref_val_0 * __forwarding ;
int __flags ;
int __size ;
int val ;
};
__block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
};
当给__block变量赋值时
^{val = 1;}
其源代码转换如下:
这里的Block中的_main_block_impl_0结构体实例持有指向——block变量的__Block_byref_val_0结构体实例指针 ;同时通过其成员变量__forwarding访问成员变量val ;
Block转换为Block的结构体类型的自动变量, __block变量转换为block变量的结构体类型的自动变量,这些都是在栈上生成的该结构体的实例变量 ;
这里我们需要了解三个类:
之前的Block例子都是_NSConcretestackBlock类的
(关于Block对象的类型,我们可以看isa成员变量的赋值)
当在记述全局变量的地方使用Block语法时,生成的Block对象是NSConcreteGlobalBlock类对象
void (^blk) (void) = ^ {printf("Global Block\n") ;} ;
int main () {
从名字是那个就可以看出,这类block是可以全局调用的,和全局变量性质差不多 ;
除了上面这种情况,当Block语法的表达式中不使用应截获的自动变量时,这时的Block也为_NSConcreteGlobalBlock类对象 ;这时的block对象分配程序的数据域中 ;
配置在全局变量上的Block,可以在作用域外通过指针安全使用,但设置在栈上的Block,如果其所属的变量作用域结束,该Block就被废弃 ;为类解决这一问题,Blocks将Block和_block变量从栈上复制到堆上 ;这样即使作用域结束,堆上的Block任然可以存在 ,这类复制生成的Block对象的类为_NSConcreteMallocBlock ;
顺便注意一下,__block变量的结构体成员__forwarding可以实现无论变量分配在栈上还是堆上时都可以正确访问————block变量 ;
当ARC有效时,大多数情形下编译器都会恰当的进行判断,自动生成将Block从栈上复制到堆上的代码 ;
typedef int (^blk_t) (int) ;
blk_t func (int rate) {
return ^ (int count) (return rate* count;} ;
通过对应的ARC编译器转换之后如下:
这里的objc——retainBlock函数实际上就是_Block_copy函数 ;
书上把这个地方解释的挺清楚的:
像上面这种情况,将Block作为函数返回值返回时,编译器会自动生成复制到堆上的代码 ;
还有一些情况需要我们手动生成代码,将Block从栈上复制到堆上:
如向方法或函数的参数中传递Block时;
举个例子:在NSArray类的initWithObjects实例方法上传递Block时需要手动复制
- (id) getBlockArray {
int val = 10 ;
return [[NSArray alloc] initwithObjects :^{NSLog(@"blk0:%d",val);},^{NSLog(@"blk0:%d",val);},nil};
}
该源代码在执行时会发生异常,需要修改一下:
- (id) getBlockArray {
int val = 10 ;
return [[NSArray alloc] initwithObjects :[^{NSLog(@"blk0:%d",val);},copy],[^{NSLog(@"blk0:%d",val);},copy],nil};
}
从上面我们也可以知道BLock类型变量也可以调用copy方法 ;
不同类的Block对象调用copy时,复制的效果也不一样 ;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。