《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 代码块和函数指针
    代码块借鉴了函数指针的语法。所以如果你知道如何声明函数指针,也就知道了如何声明一个代码块。与函数指针类似,代码块具有以下特征:
    ① 返回类型可以手动声明也可以由编译器推导;
    ② 具有指定类型的参数列表;
    ③ 拥有名称。
    函数指针的声明与代码块的声明十分类似:


    image.png

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


    image.png

    image.png

    所以,一个完整的代码块可以用如下关系式来表示它们:
    <returntype> (^blockname)(list of arguments) = ^(arguments){body;};
    ① 使用代码块
    image.png

    image.png

    ② 直接使用代码块
    image.png

    image.png

    ③ 使用typedef关键字
    image.png

    image.png

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


    image.png

    image.png

    第二个NSLog语句输出200的原因: 变量是本地的,代码块会在定义时复制并保存它们的状态。
    ⑥ 全局变量
    本地变量与代码块拥有相同的有效范围,你可以根据需要把变量标记为静态的(全局的)。
    image.png

    image.png

    ⑦ 参数变量
    代码块中的参数变量与函数中的参数变量具有同样的作用。
    ⑧ __block变量
    本地变量会被代码块作为常理获取到。如果你想修改它们的值,必须将它们声明为可修改的。
    image.png

    image.png

    有些变量是无法声明为__block类型的。它们有两个限制:
    没有长度可变的数组;
    没有包含可变长度数组的结构体。
    ⑨ 代码块内部的本地变量
    image.png

    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.这些方法不能有返回值,并且要么没有参数,要么只有一个参数对象。换句话说,你只能使用以下代码格式中的一种。
    代码如下:


    1542597050043.jpg

    image.png

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


    image.png

    二、并发队列
    并发队列适用于那些可以并行运行的任务。并发队列也遵从先入先出(FIFO)的规范,且任务可以在前一个任务结束前就开始执行。一次所运行的任务数量是无法预测的,它会根据其他运行的任务在不同时间变化。所以每次你运行同一个程序,并发任务的数量可能会是不一样的。
    注意:如果需要确保每次运行的任务数量都是一样的,可以通过线程API来手动管理线程。
    每个应用程序都有3种并发队列可以使用:高优先级(high)、默认优先级(default)和低优先级(low)。
    image.png

    三、主队列
    image.png

    因为这个队列与主线程有关,所以必须小心安排这个队列中的任务顺序,否则它们可能会阻塞主应用程序运行。通常要以同步方式使用这个队列,提交多个任务并在它们操作完毕后执行一些动作。
    四、获取当前队列
    image.png

    2.2 队列也有内存管理
    调度队列是引用计数对象。可以使用dispatch_retain()和dispatch_release()来修改队列的保留计数器的值。它们与一般对象的retain和release语句类似。你只能对你自己创建的队列使用这些函数,而无法用在全局调度队列上。事实上,如果你向全局队列发送这些消息,它们会直接被忽略掉,所以即使这样做也是无害的。如果你编写的是一个使用了垃圾回收的OSX应用程序,那么你必须手动管理这些队列。
    2.2.1 队列的上下文
    你可以向调度对象(包括调度队列)指派全局数据上下文,可以在上下文中指派任意类型的数据,比如OC对象或指针。系统只能知道上下文包含了与队列有关的数据,上下文数据的内存管理只能由你来做。你必须在需要它的时候分配内存并在队列销毁之前进行清理。在为上下文数据分配内存的时候,可以使用dispatch_set_context()和dispatch_get_context()函数。
    image.png

    ① 清理函数

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


    image.png

    1542621894089.jpg

    ② 添加任务
    有两种方式可以向队列中添加任务
    同步:队列会一直等待前面任务结束。
    异步:添加任务后,不必等待任务,函数会立刻返回。推荐优先使用这种方式,因为它不会阻塞其他代码的运行。
    你可以选择向队列提交代码块或函数。一共有4个调度函数,分别是代码块和函数各自的同步与异步方式。
    注意:如果想要避免出现死锁,那么绝对不要给运行在同一队列中的任务调用dispatch_sync或dispatch_sync_f函数。
    2.2.2 调度程序
    添加任务最简单的方法就是通过代码块。
    image.png

    image.png

    image.png

    2.3 操作队列
    被称为操作的API, 可以让队列在OC层级上使用起来更加简单。
    如果想要使用操作,首先需要创建一个操作对象,然后将其指派给操作队列,并让队列执行它。一共有3种创建操作的方式。
    ① NSInvocationOperation: 如果你已经拥有一个可以完成工作的类,并且想要在队列上执行,可以尝试使用这方法。
    ② NSBlockOperation: 这有些像包含了需要执行代码块的dispatch_async函数。
    ③ 自定义的操作:如果你需要更灵活的操作类型,可以创建自己的自定义类型。你必须通过NSOperation子类来定义你的操作。
    创建调用操作(invocation operation)
    image.png

    image.png

    创建代码块操作
    image.png

    image.png

    向队列中添加操作
    一旦创建了操作,你就需要向队列中添加代码块。这次我们将使用NSOperationQueue来取代之前使用的dispatch_queue_t函数。NSOperationQueue一般会并发执行操作。它具有相关性,因此如果某操作是基于其他操作的,它们会相应地执行。
    如果要确保你的操作是连续执行的,可以设置最大并发操作数为1,这样任务将会按照先入先出的规范执行。在向队列添加操作之前,需要某个方法来引用到那个队列。你可以创建一个新队列或使用之前已经定义过的队列。
    image.png

    小结:代码块是OC的新特性,增强了函数的功能。有了代码块,就可以通过绑定变量来创建程序中会使用到的对象。代码块在实现并发性功能时尤其方便。
    并发性很复杂,本章仅讨论对OSX和iOS程序有效的并发性功能。
    苹果公司的GCD特性提供了一种方法,你无需花很多时间在系统的低层级编码,应用程序就可以使用并发性。你应该多尝试GCD和其他并发性编程功能,以找出哪些对于你的应用程序是可行的,哪些很好用。
    随着你的水平不断提高以及苹果公司添加更多的工具,你的应用程序将能够并行执行更多的任务,从而更快地做出响应。不过,一旦超过了临界点,给应用程序添加并行的任务就会得不偿失。(花大量时间编码和调试)
    如果你经常要使用并发任务,请避免发生死锁。(任务相互关联导致程序永远无法结束)或出现其他麻烦的Bug。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容