欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

IOS --- Blocks详解(一)

程序员文章站 2024-01-21 19:49:16
...

一:什么是Blocks

  Blocks是C语言的扩充功能,用一句话表示他的扩充功能:带有自动变量(局部变量)的匿名函数。顾名思义,匿名函数就是不带有名称的函数。而C语言是不允许这样的函数存在的,即便是函数指针,也是知道函数名的。

int (*funcptr) (int) = &func;
int result = (*funcptr)(10);

  通过Block,源代码中就能使用匿名函数,到这里我们知道了“带有自动变量值的匿名函数”中“匿名函数”的概念。那么带有“自动变量值”究竟是什么呢?

//在C语言中能够使用的变量
* 自动变量(局部变量)
* 函数参数
* 静态变量 (静态局部变量)
* 静态全局变量
* 全局变量

//在函数多次调用之间能够传递值的变量有:
* 静态变量(静态局部变量)
* 静态全局变量
* 全局变量

  另外,“带有自动变量值的匿名函数”这一概念并不仅指Blocks,他还应用于许多语言中。在计算机科学中,此概念也称为闭包(Closure),lambda计算等。

二:Blocks语法

  首先先来看一个简单的块用法

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

  如上所示,完整形式的Block语法与一般的C语言函数定义相比,仅有两点不同。

  1* 没有函数名

  2* 带有^

  第一点不同没有函数名,因为它是匿名函数。第二点不同是返回值类型前带有“^”(插入记号,caret)记号。因为OS X,IOS应用程序的源代码中将大量使用Block,所以插入该记号便于查找。

//完整模式
^ 返回值类型  参数列表  表达式

//省略模式
^ 参数列表 表达式

^ 表达式

例如:

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

  表达式中的省略问题:

1. 省略返回值类型时,如果表达式中有return语句就使用该返回值的类型,如果表达式中没有return语句就使用void类型

2.如果不使用参数,参数列表也可以省略:

^void (void){printf("");}

//可以省略参数

^{printf("");}

三:Blocks 类型变量

  上面陈述到,在Block语法单从其记述方式上来看,除了没有名称以及带有“^”以外,其他的都和C语言函数定义相同。在定义C语言函数时,就可以将所定义函数的地址赋值给函数指针类型变量中。

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

int (* funcptr)(int) = &func;

  同样地,在Block语法中,可将Block语法赋值给声明为Block类型的变量中,即:源代码中一旦使用Block语法就相当于生成了可赋值给Block类型变量的“值”。Blocks中由Block语法生成的值也被称为“Block”。“Block”即指源代码中的Block语法,也指由block语法所生成的值。

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

int (^blk)(int);//Block变量的声明

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

可作为以下用途使用
 * 自动变量
 * 函数参数
 * 静态变量
 * 静态全局变量
 * 全局变量

  使用Block语法将Block赋值给Block类型变量

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

//并且,多个Block类型变量之间可以相互传值

  在函数参数和函数返回值中使用Block类型变量

//函数参数
void func(int (^blk)(int)){}

//函数返回值
int (^func()(int))
{
    return ^(int count){return count+1;};
}

  由此可见,在函数参数和返回值使用Block类型变量时,记述方式极为复杂。这时,我们可以像使用函数指针类型时那样,使用typedef解决该问题。

typedef int(^blk_t)(int);

  这样我们将blk_t声明为了类型变量,和原先对比

//原来
void func( int (^blk)(int) )

void func (blk_t)
{}


//原来
int (^func()(int))

blk_t func(){}

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

typedef int (^blk_t)(int);

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


blk_t *blkptr = &blk;
(*blkptr)(10);

四:截取自动变量值

”带有自动变量值“究竟是什么?“带有自动变量值”在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;
}

//该源代码中,Block语法的表达式使用的是他之前声明的自动变量fmt 和 val。在Blocks中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值,所以在执行Block语法后,即使改写了Block中使用的自动变量值也不会影响Block执行时自动变量的值,
所以执行结果为 val = 10;

五: __block说明符

  实际上,自动变量值截获只能保存执行Block语法瞬间的值。保存后就不能改写值。下面我们尝试在BLOCK内改写截获的自动变量值,看看会出现什么结果。

int val = 0;
void (^blk)(void) = ^{val = 1;};

blk();
printf("val = %d \n",val);

//以上为在Block语法外声明的给自动变量赋值的源代码,该源代码会产生编译错误。

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

__block int val = 0;
void (^blk)(void) = ^{val = 1;};

blk();
printf("val = %d \n",val);

  输出val = 1;

六:截获的自动变量

  如果将值赋值给Block中截获的自动变量,就会产生编译错误。

  那么截获 Objective-C对象,调用变更该对象的方法也会产生编译错误吗?

id array = [[NSMutableArray alloc] init];

void (^blk)(void) = ^{
    id obj = [[NSObject alloc]init];
    [array addObject:obj];
};
//这样是没有问题的,而向截获的变量array赋值则会产生编译错误。

//修改array的值会产生编译错误,但是使用不会产生编译错误。

注意:在使用C语言数组时必须小心使用其指针,源代码如下:

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

//只是使用C语言的字符串字面量数组,而并没有向截获的自动变量赋值,因此看似没有问题,但是实际上会产生编译错误
 error:cannot refer to declaration with an array type inside block printf("%c\n",text[2]);
 note:declared here const char text[] = "hello";

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

const char* text = "hello";
void (^blk)(void) = ^{printf(text[2]);};