Objective-C 之Block(1)

Block 语法

Blocks是C语言的扩种功能,是带有自动变量(局部变量)的匿名函数。

^void (int event){
	printf("buttonId: %d event = %d\n", i , event);
}
复制代码

与一般函数相比,有两点不同

  1. 没有函数名(匿名函数)
  2. 带有"^"(插入记号)

以下为Block语法

^ 返回值类型 参数列表 表达式

如:

^int (int count){return count+1;}
复制代码

可以省略返回值类型

^ 参数列表 表达式

省略返回值类型时,如果表达式中有return语句就使用该返回值的类型,如果表达式中没有人return语句就使用void类型。表达式中含有多个return语句时,所有return的返回值类型必须相同。

如:

^(int count){ return count+1;}
复制代码

如果不是用参数,参数列表也可以省略。

^ 表达式

如:

^void (void){ printf("Blocks\n");}
复制代码

可省略为:

^{ printf("Blocks\n");}

复制代码

Block 类型变量

在定义C语言函数时,就可以将所定义函数的地址赋值给函数指针类型的变量中。

int func(int count)
{
	return count+1;
}

int (*funcptr) (int) = &func;
复制代码

这样一来,函数func的地址就能赋值给函数指针类型变量funcptr中了。

注:引用知乎fan wang关于指针的回答,解释一下指针类型变量原理

作者:fan wang 链接:https://www.zhihu.com/question/31022750/answer/50629732 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

一图胜千言1. 声明变量:C语言声明一个变量时,编译器在内存中留出一个唯一的地址单元来存储变量,如下图,变量var初始化为100,编译器将地址为1004的内存单元留给变量,并将地址1004和该变量的名称关联起来。

2.创建指针:变量var的地址是1004,是一个数字,地址的这个数字可以用另一个变量来保存它,假设这个变量为p,此时变量p未被初始化,系统为它分配了空间,但值还不确定,如下图所示。
3.初始化指针,将变量var的地址存储到变量p中,初始化后(p=&var),p指向var,称为一个指向var的指针。指针是一个变量,它存储了另一个变量的地址。
4.声明指针:typename *p 其中typename指的是var的变量类型,可以是 short ,char ,float,因为每个类型占用的内存字节不同,short占2个字节,char占1个字节,float占4个字节,指针的值等于它指向变量的第一个字节的地址 。*是间接运算符,说明p是指针变量,与非指针变量区别开来。 5.*p和var指的是var的内容;p和&var指的是var的地址
6.既然指针*p的值等于var,p的值等于&var,为什么要多发明这一个指针符号增加记忆量呢。指针主要的功能有两个:避免副本和共享数据。指针的重要功能是函数之间传递参数。 talk is cheap, show me the code! 假设用c语言设计一个游戏,控制人物向前走的函数为 go_forward(),这个函数接收游戏人物的坐标(int x,int y) 两个变量,对这两个变量进行加减操作。

#include <stdio.h> 
void go_forward(int position_x,int position_y)
{  
	position_x=position_x+1;
	position_y=position_y+1;
}
int main()
{
	int x=0;
	int y=0;
	go_forward(x,y);
	printf("当前坐标为:%d,%d \n",x,y);
	return 0;
}
复制代码

你希望执行go_forward()函数后x,y坐标都+1,输出为(1,1),但是结果还是(0,0)原因为C语言调用函数的方式是按值传递参数,以x参数为例,刚开始main函数中有一个x的局部变量,值为0,当计算机调用go_forward()函数时,它将变量x的值复制给了参数position_x,这只是一个赋值过程将变量x赋值给变量position_x,相当于 position_x=x 命令,而这个命令,x的值是不发生变化的,结果如下图所示,x的值仍为0,position_x的值变为1。

解决方法,传递指针,用指针告诉go_forward()函数参数x的值的地址,go_forward()函数就能修改对应地址中的内容。所以用指针的主要原因是让函数共享存储器,一个函数可以修改另一个函数创建的数据,只要提供数据在内存中的地址,修改代码如下。

#include <stdio.h>
void go_foward(int *position_x,int*position_y)
{
	*position_x=*position_x+1; 
	*position_y=*position_y+1;
}
int main()
{
	int x=0;
	int y=0;
	go_forward(&x,&y);
	printf("当前坐标为:%d,%d \n",x,y);
	return 0;
}
复制代码

运行结果:当前坐标为:1,1



同样地,在Block语法下,可以将Block语法赋值给生命为Block类型的变量中。即源代码中一旦使用Block语法就相当于生成了可赋值给Block类型变量的“值”。

声明Block类型变量的示例如下:

int (^blk)(int);
复制代码

与前面的使用函数指针的源代码对比可知,声明Block类型变量仅仅是将声明函数指针类型变量的"*"变为"^"。该Block类型变量与一般C语言变量完全相同,可用作:

  • 自动变量(局部变量)
  • 函数参数
  • 静态变量
  • 静态全局变量
  • 全局变量
int (^blk)(int) = ^(int count){
	return count+1;
}
复制代码

由"^"开始的Block语法生成的Block被赋值给变量blk中。因为与通常的变量相同,所以当然也可以由Block类型变量向Block类型变量赋值。

int (^bkl1)(int) = blk;

int (^blk2)(int);
blk2 = blk1;
复制代码

在函数参数中使用Block类型变量可以向函数传递Block。

void func(int (^block)(int)){
}
复制代码

在函数返回值中指定Block类型,可以将Block作为函数的返回值返回。

int (^func()(int)){
	return ^(int count){
		return count+1;
		}
}
复制代码

由此可知,在函数参数和返回值中使用Block类型变量是,记述方式极为复杂,所以,使用typedef来解决。

typedef int (^blk_t)(int);
复制代码

如上所示,通过使用typedef可声明“blk_t”类型变量。

void func(int (^block)(int)){
}
复制代码

可以变为:

void func(blk_t blk){
}
复制代码
int (^func()(int)){
	return ^(int count){
		return count+1;
		}
}
复制代码

可以变为:

blk_t func(){
	return ^(int count){
		return count+1;
		}
}
复制代码

另外,将赋值给Block类型变量中的Block方法像C语言通常的函数调用那样使用,这种方法与使用函数指针类型变量调用函数的方法几乎完全相同。 例:变量funcptr为函数指针类型是,想下面这样调用函数指针类型变量。

int result = (*funcptr) (10);
复制代码

变量blk为Block 类型的情况下,这样调用Block类型变量:

int result = blk(10);
复制代码

在函数参数中使用Block类型变量并在函数中执行Block的例子如下:

int func(blk_t blk, int rate){
	return blk(rate);
}
复制代码

在OC中也可以:

- (int) methodUsingBlock:(blk_t) blk rate:(int)rate{
	return blk(rate);
}
复制代码

Block类型变量可完全像C语言变量一样使用,因此也可以使用指向Block类型变量的指针,即Block的指针类型变量。

typedef int(^blk_t)(int);

blk_t blk = ^(int count){ return count+1;};

blk_t *blkptr = &blk;

(*blkptr)(10);
复制代码

截获自动变量值

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语法的表达式使用的是它之前声明的自动变量fmt和val。Block中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值。因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使改写Block中使用的自动变量的值也不会影响Block执行时自动变量的值。

__block 说明符

由于Block表达式截获了所使用的自动变量的值,如果在Block中尝试修改自动变量的值会编译错误:

int val = 0;
void (^blk)(void) = ^{ val = 1; };
blk();
printf("val = %d\n",val);
复制代码

编译之后:

Variable is not assignable (missing __block type specifier)
复制代码

若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在该自动变量上附加__block说明符。

上述代码修改为:

__block int val = 0;
void (^blk)(void) = ^{ val = 1; };
blk();
printf("val = %d\n",val);
复制代码

运行结果为:

val = 1
复制代码

使用附有__block说明符的自动变量可在Block中赋值,该变量成为__block变量。

截获的自动变量

如果截获Objective-C对象,调用变更该对象的方法如下:

id array = [[NSMutableArray alloc] init];

void (^block)(void)=^{
	id obj = [[NSObject alloc] init];
	[array addObject : obj];
};
复制代码

编译运行后发现没有问题,但是向截获的变量array赋值则会产生编译错误。以上代码中截获的变量值为NSMutableArray类的对象。如果用C语言来描述,即是截获NSMutableArray类对象用的结构体实例指针。虽然赋值给截获的自动变量array的操作会产生编译错误,但使用截获的值缺不会有任何问题。

另外,在使用C语言数组时必须小心使用其指针。

const char text[] = "hello";
void (^blk)(void) = ^{
	printf("%c\n",text[2]);
};
复制代码

编译后报错:

Cannot refer to declaration with an array type inside block
复制代码

这是因为在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获,这时候,可以使用指针解决该问题。

const char *text = "hello";
void (^blk)(void) = ^{
	printf("%c\n",text[2]);
};
复制代码