Objective-C 之Block(1)

2017-12-12 12:04:28来源:https://juejin.im/post/5a2e342ef265da431440a679作者:稀土掘金人点击

分享

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


^void (int event){
printf("buttonId: %d event = %d/n", i , event);
}

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


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

以下为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]);
};

相关文章

    无相关信息

最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台