X.1 初探Block

X.1.1 宣告和使用Block

我们使用「^」运算子来宣告一个block变数,而且在block的定义最后面要加上「;」来表示一个完整的述句(也就是将整个block定义视为前面章节所介绍的简单述句,因为整个定义必须是一个完整的句子,所以必须在最后面加上分号),下面是一个block的范例:

1    1: int multiplier = 7 ;
2
3 2: int (^myBlock)( int ) = ^( int num)
4
5 3: {
6
7 4: return num * multiplier;
8
9 5: };

我们使用下图来解释这个范例(请将文字框的字翻译如下):

我们宣告一个「myBlock」变数,用「^」符号来表示这是一个block。

这是block的完整定义,这个定义将会指定给「myBlock」变数。

表示「myBlock」是一个回传值为整数(int)的block。

它有一个参数,型态也是整数。

这个参数的名字叫做「num」。

这是block的内容。

值得注意的地方是block可以使用和本身定义范围相同的变数,可以想像在上面的例子中 multiplier 和 myBlock 都是某一个函数内定义的两个变数也就是这个变数都在某个函数两个大括号「{」和「 }」中间的区块,因为它们的有效范围是相同的,因此在block中就可以直接使用 multiplier 这个变数,此外当把block定义成一个变数的时,我们可以直接像使用一般函数般的方式使用它:

 1    1: int multiplier = 7 ;
2
3 2: int (^myBlock)( int ) = ^( int num)
4
5 3: {
6
7 4: return num * multiplier;
8
9 5: };
10
11 6: printf ( "%d" , myBlock( 3 ));
12
13 7: //结果会打印出21

X.1.2 直接使用Block

在很多情况下,我们并不需要将block宣告成变数,反之我们可以直接在需要使用block的地方直接用内嵌的方式将block的内容写出来,在下面的例子中qsort_b函数,这是一个类似传统的qsort_t函数,但是直接使用block做为它的参数:

 1    1: char *myCharacters[ 3 ] = { "TomJohn" , "George" , "Charles Condomine" };
2
3 2: qsort_b (myCharacters, 3 ,
4
5 3: sizeof ( char *),
6
7 4: ^( const void *l, const void *r)//block部分
8
9 5: {
10
11 6: char *left = *( char **)l;
12
13 7: char *right = *( char **)r;
14
15 8: return strncmp (left, right, 1 );
16
17 9: } //end
18
19 10: );

X.1.3 __block 变量

一般来说,在block内只能读取在同一个作用域的变数而且没有办法修改在block外定义的任何变数,此时若我们想要这些变数能够在block中被修 改,就必须在前面挂上__block的修饰词,以上面第一个例子中的 multiplier 来说,这个变数在 block 中是唯读的,所以 multiplier = 7 指定完后,在 block 中的 multiplier 就只能是 7 不能修改,若我们在 block 中修改 multiplier ,在编辑时就会产生错误,因此若想要在 block 中修改 multiplier ,就必须在 multiplier 前面加上 __block 的修饰词,请参考下面的范例:

 1    1: __block int multiplier = 7 ;
2
3 2: int (^myBlock)( int ) = ^( int num)
4
5 3: {
6
7 4: if (num > 5 )
8
9 5: {
10
11 6: multiplier = 7 ;
12
13 7: }
14
15 8: else
16
17 9: {
18
19 10: multiplier = 10 ;
20
21 11: }
22
23 12: return num * multiplier;
24
25 13: };

X.2 Block 概要

Block 提供我们一种能够将函数程式码内嵌在一般述句中的方法,在其他语言中也有类似的概念称做「closure」,但是为了配合Objective-C的贯例,我们一律将这种用法称为「block」

X.2.1 Block 的功能

Block 是一种具有匿名功能的内嵌函数,它的特性如下:

如一般的函数般能拥有带有型态的参数。

拥有回传值。

可以撷取被定义的词法作用域(lexical scope)状态。

可以选择性地修改词法作用域的状态。

注:词法作用域(lexical scope)可以想像成是某个函数两个大括号中间的区块,这个区块在程式执行时,系统会将这个区块放入堆叠记忆体中,在这个区块中的宣告的变数就像是我们 常听到的区域变数,当我们说block可以撷取同一词法作用域的状态时可以想像block变数和其他区域变数是同一个层级的区域变数(位于同一层的堆叠 里),而block的内容可以读取到和他同一层级的其他区域变数。

我们可以拷贝一个block,也可以将它丢到其他的执行绪中使用,基本上虽然block在iOS程式开发中可以使用在C/C++开发的程式片段,也可以在Objective-C中使用,不过在系统的定义上,block永远会被视为是一个Objective-C的物件。

X.2.2 Block 的使用时机

Block 一般是用来表示、简化一小段的程式码,它特别适合用来建立一些同步执行的程式片段、封装一些小型的工作或是用来做为某一个工作完成时的回传呼叫(callback) 。

在新的iOS API中block被大量用来取代传统的delegate和callback,而新的API会大量使用block主要是基于以下两个原因:

可以直接在程式码中撰写等会要接着执行的程式,直接将程式码变成函数的参数传入函数中,这是新API最常使用block的地方。

可以存取区域变数,在传统的callback实作时,若想要存取区域变数得将变数封装成结构才能使用,而block则是可以很方便地直接存取区域变数。

X.3 宣告和建立Block

X.3.1 宣告Block的参考(Reference)

Block 变数储存的是一个block的参考,我们使用类似宣告指标的方式来宣告,不同的是这时block变数指到的地方是一个函数,而指标使用的是「*」,block则是使用「^」来宣告,下面是一些合法的block宣告:

 1    1: /* 回传void ,参数也是void 的block*/
2
3 2: void (^blockReturningVoidWithVoidArgument)( void );
4
5 3: /* 回传整数,两个参数分别是整数和字元型态的block*/
6
7 4: int (^blockReturningIntWithIntAndCharArguments)( int , char );
8
9 5: /* 回传void ,含有10 个block 的阵列,每个block 都有一个型态为整数的参数*/
10
11 6: void (^arrayOfTenBlocksReturningVoidWinIntArgument[ 10 ])( int );
12
13 7: X.3.2 建立一个Block
14
15 8:
16
17 9: 我们使用「^」来开始一个block,并在最后使用「;」来表示结束,下面的范例示范了一个block变数,然后再定义一个block把它指定给block变数:
18
19 10:
20
21 11: int (^oneFrom)( int ); /* 宣告block 变数*/
22
23 12: /* 定义block 的内容并指定给上面宣告的变数*/
24
25 13: oneFrom = ^(int anInt)
26
27 14: {
28
29 15: return anInt = - 1 ;
30
31 16: };

X.3.3 全域的Block

我在可以在档案中宣告一个全域的block,请参考以下范例:

1    1: int GlobalInt = 0 ;
2
3 2: int (^getGlobalInt)( void ) = ^ ( void ) { return GlobalInt ;};

X.4 Block 和变量

接下来的这一小节我们将会介绍block和变数之间的互动。

X.4.1 变数的型态

我们可以在block中遇到平常在函数中会遇到的变数类型:

l 全域(global)变数或是静态的区域变数(static local)。

l 全域的函数。

l 区域变数和由封闭领域(enclosing scope)传入的参数。

除了上述之外block额外支援了另外两种变数:

在函数内可以使用__block 变数,这些变数在block中是可被修改的。

汇入常数(const imports)。

此外,在方法的实作里,block可以使用Objective-C的实体变数(instance variable)。

下列的规则可以套用到在block中变数的使用:

可以存取全域变数和在同一领域(enclosing lexical scope)中的静态变数。

可以存取传入block的参数(使用方式和传入函数的参数相同)。

在同一领域的区域变数在block中将视为常数(const)。

可以存取在同一领域中以__block 为修饰词的变数。

在block中宣告的区域变数,使用方式和平常函数使用区域变数的方式相同。

下面的例子介绍了区域变数(上述第三点)的使用方式:

   1: int x = 123 ;

   2: void (^printXAndY)( int ) = ^( int y)

   3:     {

   4: printf ( "%d %d\n" , x, y); 

   5:     };

   6: // 将会印出123 456

   7:     printXAndY( 456 );

   8: 就如上面第三点所提到的,在上例中的int x = 123的变量x,在传入block后将视同常数,因此若我们在block中试着去修改x的值时就会产生错误,下面的例子将会无法通过编译: 

   9:  

  10: int x = 123 ;

  11: void (^printXAndY)( int ) = ^( int y)

  12: {

  13:     // 下面这一行是错的,因为x 在这是一个常数不能被修改。

  14:     x = x + y;

  15:     printf ( "%d %d\n" , x, y); 

  16:     };

若在block中想要修改上面的变数x,必须将x宣告加上修饰词__block,请参考接下来这一小节的介绍。

X.4.2 __block 型态变数

我们可以藉由将一个由外部汇入block的变数放上修饰词__block来让这个变数由唯读变成可以读和写,不过有一个限制就是传入的变数在记忆体 中必须是一个占有固定长度记忆体的变数,__block修饰词无法使用于像是变动长度的阵列这类不定长度的变数,请参考下面的范例:

   1: // 加上__block 修饰词,所以可以在block 中被修改。

   2: __block int x = 123 ;

   3: void (^printXAndY)( int ) = ^( int y)

   4:     {

   5:         x = x + y; 

   6: printf ( "%d %d\n" , x, y); 

   7:     };

   8: // 将会印出579 456

   9:     printXAndY( 456 );

  10: //x 将会变成 579;

  11: 下面我们使用一个范例来介绍各类型的变数和block之间的互动: 

  12:  

  13: extern NSInteger CounterGlobal;

  14: static NSInteger CounterStatic;

  15: {

  16: NSInteger localCounter = 42 ;

  17: __block char localCharacter;

  18: void (^aBlock)( void ) = ^( void )

  19:     {

  20:         ++ CounterGlobal ; //可以存取。

  21:         ++ CounterStatic ; //可以存取。 

  22: CounterGlobal = localCounter; //localCounter在block 建立时就不可变了。

  23:         localCharacter = 'a' ; //设定外面定义的localCharacter 变数。

  24:     };

  25:     ++localCounter; //不会影响的block 中的值。

  26:     localCharacter = 'b' ;

  27:     aBlock(); //执行block 的内容。

  28: //执行完后,localCharachter 会变成'a'

  29: }

X.4.3 物件和Block变数

Block 支援在Objective-C、C++物件和其他block中当作变数来使用,不过因为在大部分的情况我们都是使用Objective-C的撰写程式,因 此在这一小节我们仅针对Objective-C的情况进行介绍,至于其他两种情况就留给有兴趣的读者再自行深入研究了。

x.4.3.1 Objective-C 物件

在拥有参考计数(reference-counted)的环境中,若我们在block中参考到Objective-C的物件,在一般的情况下它将会自动增加物件的参考计数,不过若以__block为修饰词的物件,参考计数则是不受影响。

如果我们在Objective-C的方法中使用block时,以下几个和记忆体管理的事是需要额外注意的:

l 若直接存取实体变数(instance variable),self的参考计数将被加1。

l 若透过变数存取实体变数的值,则只变数的参考计数将被加1。

以下程式码说明上面两种情况,在这个假设instanceVariable是实体变数:

   1: dispatch_async (queue, ^{

   2: // 因为直接存取实体变数instanceVariable ,所以self 的retain count 会加1

   3: doSomethingWithObject (instanceVariable);

   4:     });

   5: id localVaribale = instanceVariable;

   6: dispatch_async (queue, ^{

   7: //localVariable 是存取值,所以这时只有localVariable 的retain count 加1

   8: //self 的 return count  并不会增加。

   9: doSomethingWithObject (localVaribale);

  10:     });

X.5 使用Block

这一小节我们将会对block的使用方式做一些初步的介绍

X.5.1 呼叫一个Block

当block宣告成一个变数时,我们可以像使用一般函数的方式来使用它,请参考下面两个范例:

   1: int (^oneFrom)( int ) = ^( int anInt) {

   2: return anInt - 1 ;

   3:     };

   4: printf ( "1 from 10 is %d" , oneFrom( 10 ));

   5: //结果会显示:1 from 10 is 9

   6: float (^distanceTraveled)( float , float , float ) = ^( float startingSpeed, float acceleration, float time)

   7:     {

   8: float distance = (startingSpeed * time) + ( 0.5 * acceleration * time * time);

   9: return distance;

  10:     };

  11: float howFar = distanceTraveled( 0.0 , 9.8 , 1.0 );

  12: //howFar会变成4.9

在一般常见的情况中,若是将block当做是参数传入函数,我们通常会使用「内嵌」的方式来使用block。

X.5.2 将Block当作函数的参数

我们可以像使用一般函数使用参数的方式,将block以函数参数的型式传入函数中,在这种情况下,大多数我们使用block的方式将不会倾向宣告block而是直接以内嵌的方式来将block传入,这也是目前新版SDK中主流的做法,我们将补充前面章节的例子来说明:

   1: char *myCharacters[ 3 ] = { "TomJohn" , "George" , "Charles Condomine" };

   2: qsort_b (myCharacters, 3 , sizeof ( char *),

   3:             ^( const void *l, const void *r)

   4:             {

   5: char *left = *( char **)l;

   6: char *right = *( char **)r;

   7: return strncmp (left, right, 1 );

   8:             } // 这里是block 的终点。

   9:             );

  10: // 最后的结果为:{"Charles Condomine", "George", "TomJohn"}

在上面的例子中,block本身就是函数参数的一部分,在下一个例子中dispatch_apply函数中使用block,dispatch_apply的定义如下:

   1: void

   2: dispatch_apply( size_t iterations, dispatch_queue_t queue, void (^block)( size_t ));

   3: 这个函数将一个block提交到发送伫列(dispatch queue)中来执行多重的呼叫,只有当伫列中的工作都执行完成后才会回传,这个函数拥有三个变数,而最后一个参数就是block ,请参考下面的范例: 

   4:  

   5: size_t count = 10 ;

   6: dispatch_queue_t queue =

   7: dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT , 0 );

   8: dispatch_apply (count, queue, ^( size_t i) {

   9: printf ( "%u\n" , i);

  10:     });

X.5.3 将Block当作方法的参数

在SDK中提供了许多使用block的方法,我们可以像传递一般参数的方式来传递block,下面这个范例示范如何在一个阵列的前5笔资料中取出我们想要的资料的索引值:

   1: // 所有的资料

   2: NSArray *array = [ NSArray arrayWithObjects : @"A" , @"B" , @"C" , @"A" , @"B" , @"Z" , @"G" , @"are" , @" Q" ,nil ];   

   3: // 我们只要这个集合内的资料

   4: NSSet *filterSet = [ NSSet setWithObjects : @"A" , @"B" , @"Z" , @"Q" , nil ];

   5: BOOL (^test)( id obj, NSUInteger idx, BOOL *stop);

   6: test = ^ ( id obj, NSUInteger idx, BOOL *stop) {

   7: // 只对前5 笔资料做检查

   8: if (idx < 5 ) {

   9: if ([filterSet containsObject : obj]) {

  10: return YES ;

  11:               }

  12:       }

  13: return NO ;

  14: };

  15: NSIndexSet *indexes = [array indexesOfObjectsPassingTest :test];

  16: NSLog ( @"indexes: %@" , indexes);   

  17: // 结果:indexes: <NSIndexSet: 0x6101ff0>[number of indexes: 4 (in 2 ranges), indexes: (0-1 3-4)]

  18: // 前5笔资料中,有4笔符合条件,它们的索引值分别是0-1, 3-4

X.5.4 该避免的使用方式

在下面的例子中,block是for回圈的区域变数因此在使用上必须避免将区域的block指定给外面宣告的block:

   1: // 这是错误的范例,请勿在程式中使用这些语法!!

   2: void dontDoThis() {

   3:     void (^blockArray[3])(void); // 3 个block 的阵列

   4:     for (int i = 0; i < 3; ++i) {

   5:         blockArray[i] = ^{ printf("hello, %d\n", i); };

   6:         // 注意: 这个block 定义仅在for 回圈有效。

   7:     }

   8: }

   9: void dontDoThisEither() {

  10:     void (^block)(void);

  11:     int i = random():

  12:     if (i > 1000) {

  13:         block = ^{ printf("got i at: %d\n", i); };

  14:         // 注意: 这个block 定义仅在if 后的两个大括号中有效。

  15:     }

  16:     // ...

  17: }

iOS block的用法 by -- 周傅琦君的更多相关文章

  1. ios Block详细用法

    ios Block详细用法 ios4.0系统已开始支持block,在编程过程中,blocks被Obj-C看成是对象,它封装了一段代码,这段代码可以在任何时候执行.Blocks可以作为函数参数或者函数的 ...

  2. iOS block的用法

    本章学习目标: 1. 了解何谓block. 2. 了解block的使用方法. Block 是iOS在4.0之后新增的程式语法,严格来说block的概念并不算是基础程式设计的范围,对初学者来说也不是很容 ...

  3. iOS中block的用法 以及和函数用法的区别

    ios中block的用法和函数的用法大致相同 但是block的用法的灵活性更高: 不带参数的block: void ^(MyBlock)() = ^{}; 调用的时候  MyBlock(); 带参数的 ...

  4. iOS开发:使用Block在两个界面之间传值(Block高级用法:Block传值)

    iOS开发:使用Block在两个界面之间传值(Block高级用法:Block传值)   使用Block的地方很多,其中传值只是其中的一小部分,下面介绍Block在两个界面之间的传值: 先说一下思想: ...

  5. iOS Block界面反向传值

    在上篇博客 <iOS Block简介> 中,侧重解析了 iOS Block的概念等,本文将侧重于它们在开发中的应用. Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C ...

  6. iOS block从零开始

    iOS block从零开始 在iOS4.0之后,block横空出世,它本身封装了一段代码并将这段代码当做变量,通过block()的方式进行回调. block的结构 先来一段简单的代码看看: void ...

  7. IOS NSUserDefaults 讲解 用法

    IOS NSUserDefaults 讲解 用法    NSUserDefaults适合存储轻量级的本地数据,比如要保存一个登陆界面的数据,用户名.密码之类的,个人觉得使用NSUserDefaults ...

  8. Block高级用法:Block传值UI_12(3)

    1.简单复习Block的定义.赋值.调用做学习传值铺垫: //声明一个函数 无返无参void printfHello(int a);//函数的实现void printfHello(int a){    ...

  9. iOS block 机制

    本文要将block的以下机制,并配合具体代码详细描述: block 与 外部变量 block 的存储域:栈块.堆块.全局块 定义 块与函数类似,只不过是直接定义在另一个函数里,和定义它的那个函数共享同 ...

随机推荐

  1. 编写Java程序,随机给定一个数字猜大小

    返回本章节 返回作业目录 需求说明: 由系统随机生成一个1~100之间的整数. 通过控制台一直输入一个整数,比较该数与系统随机生成的那个数,如果大就输出"猜大了.",继续输入:如果 ...

  2. Swoole 中协程的使用注意事项及协程中的异常捕获

    协程使用注意事项 协程内部禁止使用全局变量,以免发生数据错乱: 协程使用 use 关键字引入外部变量到当前作用域禁止使用引用,以免发生数据错乱: 不能使用类静态变量 Class::$array / 全 ...

  3. 解构插槽 Prop

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <link rel ...

  4. linux分区(挂载)

    主分区: 最多4个扩展分区: 最多一个: 扩展分区+主分区最多4个: 不能存放数据,只能划分逻辑分区逻辑分区: 可格式化.存放数据: 分区编号只能从5开始(1.2.3.4编号为主分区或扩展分区) 所有 ...

  5. oracle 之 cursor:创建存储过程批量执行DDL语句

    说明:使用此过程可任意执行批量DDL语句,调用DDL查询语句时,注意转义字符,使用 ' 转义! 需求:批量删除以CUR_TEST开头的表,且有日志记录. 环境准备:建几张以CUR_TEST开头测试表. ...

  6. java 使用 ArrayList 排序【包括数字和字符串】

    1.数字排序 /** * 数字排序 */ @Test public void t2() { List<Integer> list = new ArrayList<>(); li ...

  7. CSS3 动画3D视角下 旋转圆环

    首先是  transform 属性: transform 属性向元素应用 2D 或 3D 转换.该属性允许我们对元素进行旋转.缩放.移动或倾斜. 加上对应属性则可得到3D透视效果下的形态 本次以圆形( ...

  8. vue组件实现图片的拖拽和缩放

    vue实现一个组件其实很简单但是要写出一个好的可复用的组件那就需要多学习和钻研一下,一个好的组件必须有其必不可少的有优点:一是能提高应用开发效率.测试性.复用性等:二是组件应该是高内聚.低耦合的:三是 ...

  9. Javascript中定时器的使用方法

    Javascript中定时器的使用方法 1.间隔定时器(每隔一段时间执行一次代码) 格式:setInterval(函数,时间) //时间单位是毫秒,每隔设置的时间执行函数里的内容一遍(一直执行) // ...

  10. 【失败的经验】在linux下编译opencv for android

    cd /home/ahfu#选择opencv 3.4.6版本来编译wget https://github.com/opencv/opencv/archive/3.4.6.tar.gztar -zxvf ...