《Objective-C基础教程》读书笔记17—代码块和并发性

代码块,一个可以增强函数功能的Objective-C特性。你可以在运行着iOS(版本4以上)和OS X(版本10.6以上)的应用程序中使用代码块。并发性:如何让现代设备同时执行多个任务。

  1. 代码块
    代码块对象(简称为代码块)是对C语言中函数的扩展。除了函数中的代码,代码块还包含变量绑定。代码块有时也被称为闭包。
    代码块包含两种类型的绑定: 自动型与托管型。自动绑定使用的是栈中的内存,而托管绑定是通过堆创建的。
    因为代码块底层实际上是由C语言实现的,所以它们在各种以C作为基础的语言内都是有效的,包括Objective-C、C++以及Objective-C++。
    代码块在Xcode的GCC和Clang工具中是有效的,但它不属于ANSI的C语言标准。
    1.1 代码块和函数指针
    代码块借鉴了函数指针的语法。所以如果你知道如何声明函数指针,也就知道了如何声明一个代码块。与函数指针类似,代码块具有以下特征:
    ① 返回类型可以手动声明也可以由编译器推导;
    ② 具有指定类型的参数列表;
    ③ 拥有名称。
    函数指针的声明与代码块的声明十分类似:

    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png

    完整的代码块的定义以及代码块的内容:

    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    所以,一个完整的代码块可以用如下关系式来表示它们:
    <returntype> (^blockname)(list of arguments) = ^(arguments){body;};
    ① 使用代码块
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    ② 直接使用代码块
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    ③ 使用typedef关键字
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png

    ④ 代码块和变量
    代码块被声明后会捕捉创建点时的状态。代码块可以访问函数用到的标准类型的变量:
    全局变量,包括在封闭范围内声明的本地静态变量。
    全局函数(明显不是真实的变量)。
    封闭范围内的参数。
    函数级别(即与代码块声明时相同的级别)的__block变量。它们是可以修改的变量。
    封闭范围内的非静态变量会被获取为常量。
    Objective-C的实例变量。
    代码块内部的本地变量。
    ⑤ 本地变量
    本地变量就是与代码块在同一范围内声明的变量。

    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    第二个NSLog语句输出200的原因: 变量是本地的,代码块会在定义时复制并保存它们的状态。
    ⑥ 全局变量
    本地变量与代码块拥有相同的有效范围,你可以根据需要把变量标记为静态的(全局的)。
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    ⑦ 参数变量
    代码块中的参数变量与函数中的参数变量具有同样的作用。
    ⑧ __block变量
    本地变量会被代码块作为常理获取到。如果你想修改它们的值,必须将它们声明为可修改的。
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    有些变量是无法声明为__block类型的。它们有两个限制:
    没有长度可变的数组;
    没有包含可变长度数组的结构体。
    ⑨ 代码块内部的本地变量
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    1.2 Objective-C 变量
    代码块是Objective-C语言中的优秀公民,你可以像使用其他对象一样使用它。使用时会遇到的最大问题就是内存管理。在代码块中访问Objective-C变量时必须小心。
    以下规则能帮助你处理内存管理。
    ① 如果引用了一个Objective-C对象,必须要保留它。
    ② 如果通过引用访问了一个实例变量,要保留一次self(即执行方法的对象)。
    ③ 如果通过数值访问了一个实例变量,变量需要保留。
    注意:如果在代码块内直接访问实例变量, 则需要保留一次self, 即执行方法的对象。如果在代码块内间接访问实例变量,则变量本身需要保留。
    因为代码块是对象,所以可以向它发送任何与内存管理有关的消息。在C语言级别中,必须使用Block_cpoy()和Block_release()函数来适当地管理内存。

  2. 并发性
    用来运行Xcode的Mac电脑的处理器至少拥有两个核心,也可能更多。现在最新的iOS设备都是多核的。这意味着你可以在同一时间进行多项任务。苹果公司提供了多种可以利用多核特性的API。能够在同一时间执行多项任务的程序称为并发的程序。
    利用并发性最基础的方法是使用POSIX线程来处理程序的不同部分使其能够独立执行。POSIX线程拥有支持C语言和Objective-C的API。编写并发性程序需要创建多个线程,而编写线程代码是很有挑战性的。因为线程是级别较低的API, 你必须手动管理。根据硬件和其他软件运行的环境,需要的线程数量会发生变化。处理所有的线程是需要技巧的。
    为了减轻在多核上编程的负担,苹果公司引入了GCD。这个技术较少了线程管理的麻烦。如果想要使用GCD, 你需要提交代码块或函数作为线程来运行。GCD是一个系统级别的技术,因此你可以在任意级别的代码块中使用它。GCD决定需要多少线程并安排它们运行的速度。因为它是运行在系统级别上的,所以可以平衡应用程序所有内容的加载,这样可以提高计算机或设备的执行效率。
    2.1 同步
    我们如何在由多核组成的通路中管理交通呢?可以使用同步装置,比如在通道入口立一个标记(flag)或一个互斥(mutex)。
    注意:mutex是mutual exclusion的缩写,它指的是确保两个线程不会在同一时间进入临界区。
    OC提供了一个语言级别的关键字@synchronized。这个关键字拥有一个参数,通常这个对象是可以修改的。它可以确保不同的线程会连续地访问临界区的代码。
    nonatomic关键字修饰属性,是为了提高性能,而如果用atomic修饰,这样设置代码和变量会产生一些消耗,它会比直接访问更慢一些。
    ① 选择性能
    如果你只想让一些代码在后台执行,NSObject也提供了方法。这些方法的名字中都有performSelector:, 最简单的就是performSelectorInBackground:withObject:了,它能在后台执行一个方法。它通过创建一个线程来运行方法。定义这些方法时必须遵从以下限制。
    1.这些方法运行在各自的线程里,因此你必须为这些Cocoa对象创建一个自动释放池,而主自动释放池是与主线程相关的。
    2.这些方法不能有返回值,并且要么没有参数,要么只有一个参数对象。换句话说,你只能使用以下代码格式中的一种。
    代码如下:

    《《Objective-C基础教程》读书笔记17—代码块和并发性》 1542597050043.jpg
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png

    当方法执行结束后,OC运行时会特地清理并弃掉线程。需要注意:方法执行结束后并不会通知你:这是比较简单的代码。
    ② 调取队列
    GCD可以使用调度队列(dispatch queue)。它与线程很相似但使用起来更简单。只需写下你的代码,把它指派为一个队列,系统就会执行它了。你可以同步或异步执行任意代码。一共有以下3种类型的队列。
    连续队列:每个连续队列都会根据指派的顺序执行任务。你可以按自己的想法创建任意数量的队列,它们会并行操作任务。
    并发队列:每个并发队列都能并发执行一个或多个任务。任务会根据指派到队列的顺序开始执行。你无法创建连续队列,只能从系统提供的3个队列内选择一个来使用。
    主队列:它是应用程序中有效的主队列,执行的是应用程序的主线程任务。
    一、连续队列
    有时有一连串任务需要按照一定的顺序执行,这时便可以使用连续队列。任务执行顺序为先入先出(FIFO): 只要任务是异步提交的,队列会确保任务根据预定顺序执行。这些队列都是不会发生死锁的。
    死锁(deadlock)是一个令人不悦的情况,指的是两个或多个任务在等待他方运行结束。

    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    二、并发队列
    并发队列适用于那些可以并行运行的任务。并发队列也遵从先入先出(FIFO)的规范,且任务可以在前一个任务结束前就开始执行。一次所运行的任务数量是无法预测的,它会根据其他运行的任务在不同时间变化。所以每次你运行同一个程序,并发任务的数量可能会是不一样的。
    注意:如果需要确保每次运行的任务数量都是一样的,可以通过线程API来手动管理线程。
    每个应用程序都有3种并发队列可以使用:高优先级(high)、默认优先级(default)和低优先级(low)。
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    三、主队列
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    因为这个队列与主线程有关,所以必须小心安排这个队列中的任务顺序,否则它们可能会阻塞主应用程序运行。通常要以同步方式使用这个队列,提交多个任务并在它们操作完毕后执行一些动作。
    四、获取当前队列
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    2.2 队列也有内存管理
    调度队列是引用计数对象。可以使用dispatch_retain()和dispatch_release()来修改队列的保留计数器的值。它们与一般对象的retain和release语句类似。你只能对你自己创建的队列使用这些函数,而无法用在全局调度队列上。事实上,如果你向全局队列发送这些消息,它们会直接被忽略掉,所以即使这样做也是无害的。如果你编写的是一个使用了垃圾回收的OSX应用程序,那么你必须手动管理这些队列。
    2.2.1 队列的上下文
    你可以向调度对象(包括调度队列)指派全局数据上下文,可以在上下文中指派任意类型的数据,比如OC对象或指针。系统只能知道上下文包含了与队列有关的数据,上下文数据的内存管理只能由你来做。你必须在需要它的时候分配内存并在队列销毁之前进行清理。在为上下文数据分配内存的时候,可以使用dispatch_set_context()和dispatch_get_context()函数。
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    ① 清理函数

    设置完上下文对象的数据之后,什么时候清理呢?你不需要真得知道上下文对象在何时何地会被弃用。如果想解决上下文对象的清理问题,你可以让对象在它弃用的时候调用一个函数,就像类里面的dealloc函数。函数的格式应该如下所示。(道理是一样的,不需要知道它何时何地弃用或者销毁,但只需要在它弃用或者销毁的函数里进行相应内存管理的处理即可。)

    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 1542621894089.jpg
    ② 添加任务
    有两种方式可以向队列中添加任务
    同步:队列会一直等待前面任务结束。
    异步:添加任务后,不必等待任务,函数会立刻返回。推荐优先使用这种方式,因为它不会阻塞其他代码的运行。
    你可以选择向队列提交代码块或函数。一共有4个调度函数,分别是代码块和函数各自的同步与异步方式。
    注意:如果想要避免出现死锁,那么绝对不要给运行在同一队列中的任务调用dispatch_sync或dispatch_sync_f函数。
    2.2.2 调度程序
    添加任务最简单的方法就是通过代码块。
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    2.3 操作队列
    被称为操作的API, 可以让队列在OC层级上使用起来更加简单。
    如果想要使用操作,首先需要创建一个操作对象,然后将其指派给操作队列,并让队列执行它。一共有3种创建操作的方式。
    ① NSInvocationOperation: 如果你已经拥有一个可以完成工作的类,并且想要在队列上执行,可以尝试使用这方法。
    ② NSBlockOperation: 这有些像包含了需要执行代码块的dispatch_async函数。
    ③ 自定义的操作:如果你需要更灵活的操作类型,可以创建自己的自定义类型。你必须通过NSOperation子类来定义你的操作。
    创建调用操作(invocation operation)
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    创建代码块操作
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    向队列中添加操作
    一旦创建了操作,你就需要向队列中添加代码块。这次我们将使用NSOperationQueue来取代之前使用的dispatch_queue_t函数。NSOperationQueue一般会并发执行操作。它具有相关性,因此如果某操作是基于其他操作的,它们会相应地执行。
    如果要确保你的操作是连续执行的,可以设置最大并发操作数为1,这样任务将会按照先入先出的规范执行。在向队列添加操作之前,需要某个方法来引用到那个队列。你可以创建一个新队列或使用之前已经定义过的队列。
    《《Objective-C基础教程》读书笔记17—代码块和并发性》 image.png
    小结:代码块是OC的新特性,增强了函数的功能。有了代码块,就可以通过绑定变量来创建程序中会使用到的对象。代码块在实现并发性功能时尤其方便。
    并发性很复杂,本章仅讨论对OSX和iOS程序有效的并发性功能。
    苹果公司的GCD特性提供了一种方法,你无需花很多时间在系统的低层级编码,应用程序就可以使用并发性。你应该多尝试GCD和其他并发性编程功能,以找出哪些对于你的应用程序是可行的,哪些很好用。
    随着你的水平不断提高以及苹果公司添加更多的工具,你的应用程序将能够并行执行更多的任务,从而更快地做出响应。不过,一旦超过了临界点,给应用程序添加并行的任务就会得不偿失。(花大量时间编码和调试)
    如果你经常要使用并发任务,请避免发生死锁。(任务相互关联导致程序永远无法结束)或出现其他麻烦的Bug。

    原文作者:天山雪莲_38324
    原文地址: https://www.jianshu.com/p/59c5e13b95f0
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞