![iOS开发:从零基础到精通](https://wfqqreader-1252317822.image.myqcloud.com/cover/796/26793796/b_26793796.jpg)
第5章 Objective-C语言特性
5.1 代码块
5.1.1 Block简介
代码块(Block)是从iOS 4开始引入的一个新特性。Block是对C语言的一个扩展,在Objective-C中完全支持。Block在现在的iOS开发中使用越来越普遍,因为Block使用起来非常强大,简单来说,Block就是封装了一组代码语句的对象,可以在任何时间执行。
1.Block简介
Block在官方文档中的定义是这样的:Block块是封装工作单元的对象,是可以在任何时间执行的代码段。其本质上是可移植的匿名函数,可以作为方法和函数的参数传入,可以从方法和函数中返回。
Block是对C语言的一种扩展,它并未作为标准的ANSI C所定义的部分,而是由苹果公司添加到语言中的。Block看起来更像是函数,可以给Block传递参数,Block也可以具有返回值。
在iOS 4以后,越来越多系统框架的API在使用Block。苹果对于Block的使用主要集中在以下几个方面:
- 完成处理(Completion Handlers);
- 通知处理(Notification Handlers);
- 错误处理(Error Handlers);
- 枚举(Enumeration);
- 动画与形变(View Animation and Transitions);
- 分类(Sorting);
- 线程管理(GCD/NSOperation)。
2.Block的定义与调用
块是以插入字符^开头,后面的一个括号内表示块所需要的参数,最后面的大括号中是块主体,最后以分号结束,如下代码所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T127_15227.jpg?sign=1739291555-Ey9smj2o39xHqnLwRFjXskEDVJ4QBkCO-0-16a21fa9f66720ac773e9f23f24fc436)
同时,也可以将这个块赋值给一个变量printBlock,声明方式如下。其中,变量printBlock就是指向代码块的指针。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T128_15363.jpg?sign=1739291555-X8vJNSkR7qapAu63bkdTazILDacZBgMe-0-34a92ba6ac3fa982937dabc420723f66)
如下代码,定义了一个变量printBlock,这个变量指向一个Block,Block位于等号右边。这个Block执行时,需要提供一个int型的参数,同时会返回一个int型的返回值。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T128_15365.jpg?sign=1739291555-1HEtBWOx5EEFV3nPObUqHvUMVZFkRnql-0-54466d3d3af7e12dd4d227af8de28bac)
当需要调用已经定义的Block时,可以使用如下方式,和函数调用十分类似。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T128_15367.jpg?sign=1739291555-5nmEFLNzJvVJFnqPvt2rZO3RVbdFfoFt-0-2ea184caa6b0dc789d3a49029f72e646)
3.把Block声明为类的属性
由于Block就是一个存储了一段代码的对象,因此,也可以把Block设置为某个类的属性。Block属性与其他类型的属性,如NSString、NSArray,没有什么本质区别,都可以使用点语法来对属性进行取值和赋值。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T128_15369.jpg?sign=1739291555-1pS1pqROV1nLnMG7gjOSzKl9UqavcVaI-0-7a2c7446f47708b6fa5386c535004858)
注意:当声明一个Block类型的属性时,需要使用属性关键字copy。
在下面的示例代码中,添加了两个Block属性,在程序运行过程中,为两个Block属性进行赋值,即指定了一段代码,然后调用执行Block中的代码。
- 新建一个Single View Application工程,在ViewController.h文件中,声明两个Block属性。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T128_15371.jpg?sign=1739291555-7mHecLptNOCYJGMSUZVxt5ACyBmTbVRO-0-0753845c525a3697002cd807f6d40c98)
- 在ViewController.m文件中,通过点语法为两个Block属性赋值,然后再调用Block中的代码。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T128_15373.jpg?sign=1739291555-pJMW5qa3nKaIreUCvIqad0t2xm3Pc1kB-0-a6bddd02b3b085bcf2c770a2828768d5)
运行结果如图5-1所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P129_15493.jpg?sign=1739291555-VYWGNk6Ud5Mrsb9HPm3nI7Ky1KLytZp7-0-ba66bb6b3a14dbafcd3821a17520a479)
图5-1 运行结果
5.1.2 Block的参数与返回值
定义Block时,可以对Block的输入参数以及返回值的类型进行定义。可以有输入参数,也可以没有输入参数;可以设置一个输入参数,也可以设置多个参数;可以有返回值,也可以没有返回值。
1.无输入参数+无返回值
这种形式的Block,无须任何输入参数,并且无返回值,一般都是在该Block中完成一些动作。例如在UIView类中定义的animateWithDuration:animations:方法,当调用该方法时,在指定的时间(duration参数)内,完成Block((void (^)(void))animations参数)中定义的动画播放。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T129_15498.jpg?sign=1739291555-7sM6PoSeQuawx6V4GLJt6pNPkX77GtYI-0-0b95b4c357715de4744ee38241aa65d0)
这里也可以自定义一个无输入参数、无返回值的Block,如下所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T129_15500.jpg?sign=1739291555-N8eVvr2cjbGgCdXA79j2S90gZFgLSNf8-0-9303cc46dfc4325ceb40687713ee7ca2)
运行结果如图5-2所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P130_15602.jpg?sign=1739291555-A6N2wVRWWZwKbkeK5a1UNUQdkVKaAfPx-0-53c5f8ce39eeafa169f20f505ed7fb11)
图5-2 运行结果
2.有输入参数+无返回值
这种形式的Block,有输入参数,但无返回值。一般都是在该Block中根据输入参数完成一些动作,例如,在AFNetworking框架提供的如下方法,需要传入3个Block参数。当获取到网络反馈的数据后,会调用一个Block,该Block没有返回值,但是存在两个参数,其中一个是从服务器获取的数据(responseObject)。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T130_15606.jpg?sign=1739291555-goVaX6hq670fxS1DX8V7AbQVbDPoVDh1-0-d179e37ac623d1798ad3393381fb384c)
这里也可以自定义一个有输入参数、无返回值的Block,如下所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T130_15608.jpg?sign=1739291555-xVxrO90fcQ8fsUVxHMTv5ptFSyLhOosK-0-57aab73a2dfaab50230bf4699d93d322)
运行结果如图5-3所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P130_15610.jpg?sign=1739291555-YZTGIUipCL2AvxUAAzHESPOIZK1Fc9vw-0-becd00de947a81f8690b1e37cc50af69)
图5-3 运行结果
3.有输入参数及返回值
Block中可以既有参数也有返回值,此时,需要在Block封装的代码中根据返回值的类型要求提供Block的返回值。例如,下方的示例代码中,blockWithOutputAndInput返回的是参数的平方(inputNum*inputNum)。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T131_82846.jpg?sign=1739291555-TN2ZQR9xKKvRfBRcUesQGr0I8ruRNvvR-0-eb85b27240f5822ee2bbee13fdf8e0ed)
运行结果如图5-4所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P131_15753.jpg?sign=1739291555-woOlZHQnYrjL0caud3c3PWv0QqUtjVld-0-b9d661a237e2a632774dd32b6818ca72)
图5-4 运行结果
4.有多个输入参数
在Block中可以定义传入多个参数,多个参数之间使用逗号进行分隔。下面的示例代码中,Block需要传入两个Double类型的参数,这两个参数相乘后的乘积作为返回值。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T131_15757.jpg?sign=1739291555-9fPsVjOtBwJxp4dfz5teBc8Xvkpvr3w3-0-acf3f79695aece603740ca4622c5f909)
运行结果如图5-5所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P131_15759.jpg?sign=1739291555-8tnz6T9l7afaV57RIM3gxK2sS8TdOp9P-0-a2c8d85f7761e4fa500e597038330880)
图5-5 运行结果
5.无输入参数+有返回值
最后一种情况是无参数有返回值的Block。如下所示,定义了一个Block,其没有参数,但是会有int型的返回值。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T132_15892.jpg?sign=1739291555-H3yGeZIJyrNTBRGaCtuPsMhAFp4o5xHk-0-886936b8e9ca6fe466b2a06b9906376a)
运行结果如图5-6所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P132_82847.jpg?sign=1739291555-UzvlKgjBAIZegU5KvfaptgzKhs6fXaS7-0-0f451337cf8aebd0e4c394d5e647d857)
图5-6 运行结果
5.1.3 操作Block外部的变量
在使用Block时,有时会涉及修改Block定义之外的对象,为了能够修改定义在Block之外的对象,必须在该对象声明时,添加_ _block关键字(两个下划线)。
1.访问Block之外的变量
如果在一个方法中声明了Block,那么Block中也可以访问在该方法中定义的变量,前提是该变量的定义在Block定义之前。如下所示,定义了一个int型的变量i,在名称为beginBlock的Block中,可以访问i值。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T132_15899.jpg?sign=1739291555-Xhu0hQ750V6waMdAWjJRiwbXd0HkGj1f-0-f6057670b3d79a9a1cf4aebb067fbbd7)
在上述代码中,Block可以访问i的值,但是当i值发生改变的时候(i=200),再次调用Block打印的还是原来的i值(100)。也就是说,在Block定义时,会“捕捉”一次Block中使用的对象i,当i发生变化的时候,不会影响已经“捕捉”到的值。
上述案例的运行结果如图5-7所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P133_15995.jpg?sign=1739291555-cGLzItoRF1G5Sew2PGtCS37TRhmNFOeX-0-c8f63641723f736987fa66e5fa4a82bd)
图5-7 运行结果
同时需要注意的是,此时在Block中是不能对i值进行修改的。假如修改Xcode会报错,如图5-8所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P133_82849.jpg?sign=1739291555-JEhb7Q2iJhUqTWXgCPD98sFFE6HlKydy-0-ccf9a7d1b17270fbea12883e9c2e8dc8)
图5-8 程序报错提示
2.修改Block之外的变量
在Block中,假如需要更新在Block之外定义的变量,那么在定义变量时,必须加上_ _block关键字(两个下划线)。如果这样定义,以上面的代码为例,当i的值发生变化时,Block中“捕捉”的i值会随时变化。这个在实际开发中比较常用,如下所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T133_16003.jpg?sign=1739291555-GggBv9gJIlAgsTWriGFWe8tQJd0dqZZd-0-d9d3eb63c72ae3833cff4169ead717c0)
运行结果如图5-9所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P134_16112.jpg?sign=1739291555-j0PsBAg3lxWTKUa9pKjwO4KfQA97llzz-0-f8a8622d48586ebd3f3c116b35aa040c)
图5-9 运行结果
同时需要注意的是,此时在Block中可以对i的值进行修改,并且编译器也不会报错,如下所示。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T134_16116.jpg?sign=1739291555-QyRVDWdwB4smVSBwsR6TEvm2rvu0g5Qd-0-3c1d88e51e3d90607b226e37a8b7dafd)
运行结果如图5-10所示。可以看到,每次执行Block后,i值都会在Block中修改为200,因此最后打印的i值是200。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P134_16118.jpg?sign=1739291555-6WqvR8w6dPr9vpy2QmSkaFghmLEftKGa-0-1735b9d36a43596c88dbe61a780c21c9)
图5-10 运行结果
5.1.4 Block回调
在iOS的开发过程中,Block的回调使用非常普遍,也是Block的重要用法之一,在使用过程中经常可以用于替换代理的实现方法。例如,当一段动画播放完成后,执行一段代码,当得到请求的网络数据后,执行一段对数据的操作代码等。这些场景中,都使用到了Block的回调机制。Block的回调机制,可以使代码的编写变得十分清晰,提升了代码的可读性。
当需要定义回调Block时,通常情况下可以按照如下步骤进行:
- 定义带Block参数的方法。
- 设置Block的回调时机。
- 定义Block中需要执行的操作。
下面通过一个实际的例子来实践一下Block的回调实现方法。
- 创建一个Single View Application类型的工程。
- 定义带Block参数的方法。创建一个Task类,继承自NSObject。在Task.h文件中,添加如下的方法,在该方法中,设置一个Block作为参数。其中,(void(^)(void))表示为一个没有参数和返回值的Block。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T135_16229.jpg?sign=1739291555-M6akMBBvuL2SnYl1uhs9pxVdRF5nLZtl-0-2988b25c41ab48b0da2c9126444047a2)
- 设置Block的回调时机。在Task.m文件中,实现该方法。下面的代码中,当方法被调用时,会打印一行Log,提示任务开始。3秒后,会调用Block中的代码。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T135_16231.jpg?sign=1739291555-sHqK1X8hPURUeJ31Ee8G9FId5N9V8SM3-0-c44f226096c8096fdad9e14030c1286e)
- 定义Block中需要执行的操作。在上面代码的实现过程中,最关键的是定义了Block的调用时机,但没有定义Block的代码内容。Block中的代码内容,可以在使用该方法时进行赋值。在工程的ViewController.m文件中,导入Task.h头文件,并添加下面的代码,当执行到Block时,打印一行日志,提示任务完成。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-T135_16233.jpg?sign=1739291555-HKkoo6DBdtRUhETxO8I5gv8lVPO7ncOK-0-dd6319c7c3fd78bc3af6dbab49fc1d4b)
运行结果如图5-11所示,通过两行日志执行的位置以及执行的时间,可以验证Block回调的使用方法。
![](https://epubservercos.yuewen.com/D4B438/15253388904120706/epubprivate/OEBPS/Images/Figure-P135_16235.jpg?sign=1739291555-ubLnYegOH9kM9WM2aKpM9LAopbMTrxXi-0-3dc703fedfe95788c4e45547869dd3b2)
图5-11 运行结果