Objective-C 高级编程-block

在前两篇中,我们介绍了一些关于C语言的重要概念,指针struct,这些基础知识是我们深入学习block的前提,在这里将尽可能的将关于block的相关知识讲解的详细一些,分享给那些想要提升自己的小伙伴们,文章会比较长,一下子写完实在是有些力不从心,所以会采取不断更新的方式。

首先看一下什么是Blocks

Blocks是C语言的扩充功能。可以用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。

顾名思义,所谓匿名函数就是不带有名称的函数。C语言的标准不允许存在这样的函数。例如如下的源代码:

int func(int count);

它声明了名为func的函数。下面的源代码中为了调用该函数,必须使用该函数的名称func。

int result = func(10);

如果像下面这样,使用函数指针来代替直接调用函数,那么似乎不用知道函数名也能够使用该函数。

int result = (*funcptr)(10);

但其实使用函数指针也仍然要知道函数名称。像以下的源代码一样,在赋值给函数指针时,若不使用想赋值的函数的名称,就无法获得该函数的地址。

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

而通过Blocks,源代码中就能够使用匿名函数了,即不带名称的函数。对于程序员而言,命名就是工作的本质,函数名、变量名、方法名、属性名、类名和框架等都必须具备。而能够编写不带名称的函数对程序员来说相当具有吸引力。

到这里,我们知道了“带有自动变量值的匿名函数”中“匿名函数”的概念。那么“带有自动变量值”究竟是什么呢?

首先回顾一下在C语言的函数中可能使用的变量。(以下的变量在内存中的分配是不一样的,可以参考这篇文章了解 C语言中的内存分配。)

  • 自动变量(局部变量)
  • 函数的参数
  • 静态变量(静态局部变量,方法中用static声明的变量)
  • 静态全局变量(方法外用static声明的变量)
  • 全局变量

其中,在函数的多次调用之间能够传递的变量有:

  • 静态变量(静态局部变量)
  • 静态全局变量(只能在本文件中使用的全局变量)
  • 全局变量(外接也可以使用的全局变量)
    虽然这些变量的作用域不同,但在整个程序中,一个变量总保持在一个内存区域。因此,虽然多次调用函数,但该变量值总能保持不变,在任何时候以任何状态调用,使用的都是同样的变量值。

另外,“带有自动变量值的匿名函数”这一概念并不仅指Blocks,它还存在于其他许多程序语言中。

程序语言 Block的名称
C + Blocks Block
Smalltalk Block
Ruby Block
LISP Lambda
Python Lambda
C++11 Lambda
Javascript Anonymous function

Blocks 模式

一、Block语法

下面我们详细讲解一下带有自动变量值得匿名函数Block的函数,即Block表达式语法。先看一个简单点的形式:

^(int event){
        printf("event:%d",event);
}

实际上,上述Block语法使用了省略方式,其完整形式如下:

^void(int event){
        printf("event:%d",event);
}

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

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

Block的一般范式如下

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

返回值类型同C语言函数的返回值类型,“参数列表”同C语言的参数列表,“表达式”同C语言函数中允许使用的表达式。当然与C语言函数一样,表达式中含有return语句时,其类型必须与返回值类型相同。

例如写出如下形式的Block语法:

^ int (int count){return count+1};
返回值的类型可以省略

虽然前面出现过省略方式,但Block的语法可省略好几个项目。首先是返回值类型,如下所示:

^ 参数列表 表达式(返回值类型被省略)

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

前面的^ int (int count){return count+1};源代码省略其返回值类型时如下所示:

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

该block语法将按照return语句的类型,返回int形的返回值。

如果不使用参数,参数列表也可以省略。以下为不使用参数的Block语法:
^ void(void){printf("blocks\n")};

可以进一步的省略:

^ {printf("blocks\n")};
二、Block类型变量

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

int func(int count){
        return count+1;
}
int (*funcptr)(int) = &func;

这样一来,函数func的地址就能赋值给函数指针类型变量funcptr中了。
同样地,在Block语法下,可将Block语法赋值给声明为Block类型的变量中。即源代码中一旦使用Block语法就相当于生成了可赋值给Block类型变量的“值”。Blocks中由Block语法生成的值也被称为“Block”。在有关Blocks的文档中,“Block”既指源代码中的Block语法,也指由Block语法所生成的值。

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

int (^blk)(int);

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

  • 自动变量
  • 函数参数
  • 静态变量
  • 静态全局变量
  • 全局变量

那么,下面我们就试着使用Block语法将Block赋值为Block类型变量。

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

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

int (^blk1)(int) = blk;//声明一个block类型变量blk1,并将blk赋值给blk1。
int (^blk2)(int);//声明一个block类型变量blk2。
blk2 = blk1;

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

void func(int(^blk)(int)){}//int(^blk)(int)可以看做是一个block类型的变量blk。

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

int (^func())(int){
return ^(int count){
      return count+1
};//block类型为int (^func()),参数列表为空,不是没有参数,而是可以任意参数。返回的^(int count){return count+1}可以作为是上述类型的block变量。
}

从上面可以看到,在函数参数和返回值中使用Block类型变量时,记述方式比较复杂,这时候,可以向使用函数指针类型时那样,使用typedef来解决该问题。

typedef int(^blk_t)(int);

如上所示,通过使用typedef可以声明“blk_t”类型变量。我们试着在上面例子中的函数参数和函数返回值部分里使用一下。


/*原来的记述方式
void func(int (^blk)(int))
*/
//现在的
void func(blk_t blk)

/*原来的记述方式
int (^func()(int))
*/
//现在的
blk_t func();

通过使用typedef,函数定义就变得更容易理解了。
另外,将赋值给Block类型变量中Block方法像C语言通常的函数调用那样使用,这种方法与使用函数指针类型变量调用函数的方法几乎完全相同。变量funcptr为函数指针类型时,像下面这样调用函数指针类型变量。

int result = (*funcptr)(10);

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

int result = blk(10);

通过Block类型变量调用Block与C语言通常的函数调用没有区别。在函数参数中使用Block类型变量并在函数中执行Block的例子如下:

int func(blk_t blk,int rate){
return blk(rate);
}

当然,在Objective-C的方法中也可使用

- (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);

由此可知Block类型变量可像C语言中其他类型变量一样使用。

三、截获自动变量值

通过Block的语法和Block类型变量的说明,我们已经理解了“带有自动变量值的匿名函数”中的“匿名函数”。而“带有自动变量值”究竟是什么呢?“带有自动变量值”在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中使用的自动变量的值也不会影响Block执行时自动变量的值。该源代码就在Block语法后改写了Block中的自动变量val和fmt。下面我们一起看下执行结果。

val = 10;

执行结果并不是改写后的值,而是执行Block愈发时候的自动变量的瞬间值。该Block语法在执行时,字符串指针被赋值到自动变量fmt中,int值10被赋值到自动变量val中,因此这些值被保存(即被截获),从而在执行块时使用。
这就是自动变量值的截获。


四、__block 说明符

实际上,自动变量值截获只能保存执行Block语法瞬间的值。保存后就不能改写该值。下面我们尝试改写截获的自动变量值,看看会出现什么结果。下面的源代码中,Block语法之前声明的自动变量val值被赋予1。

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

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

error: variable is not assignable(missing __block type specifier)
void (^blk)(void) =  {val  =  1; }; 
                      ~~~  ^

若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在该自动变量上附加__block说明符。该源代码中,如果给自动变量声明int val附加__block说明符,就能实现在Block内赋值。

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

该源代码的执行结果为

val = 1;

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

五、 截获的自动变量

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

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

该源代码会产生以下编译错误:

error: variable is not assignable(missing __block type specifier)
void (^blk)(void) =  {val  =  1; }; 
                      ~~~  ^

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

  id array = [[NSMutableArray alloc]init];
  void (^blk)(void) = ^{
    id obj = [[NSObject alloc]init];
    [array addObject:obj];
  };

这是没有问题的,而向截获的变量array赋值会产生编译错误。该源代码中截获的变量值为NSMutableArray类的对象。如果用C语言来描述,即是截获NSMutableArray类对象用的结构体实例指针。虽然赋值给介乎跌自动变量array的操作会产生编译错误,但使用截获的值却不会有任何问题。下面的源代码向截获的自动变量进行赋值,因此会产生编译错误。

  id array = [[NSMutableArray alloc]init];
  void (^blk)(void) = ^{
        array = [[NSMutableArray alloc]init];
  };

error: variable is not assignable(missing __block type specifier)
        array = [[NSMutableArray alloc]init];
         ~~~  ^

这种情况下,需要给截获的自动变量附加__block说明符。

__block id array =  [[NSMutableArray alloc]init];
  void (^blk)(void) = ^{
        array = [[NSMutableArray alloc]init];
  };

接下来会介绍block是如何实现的,会从它的c++源码层面进行分析。(未完待续。。)

参考书籍:Objective-C高级编程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容