Main Queue
这个队列在主线程上执行它的所有任务,Cocoa 和 Cocoa Touch 允许程序员在主线程上调用一切 UIrelated 方法。使用 dispatch_get_main_queue 函数检索到主队列的句柄。
Concurrent Queues
为了执行异步和同步任务,你可以在 GCD 中检索到这写队列。多个并发队列能够轻而易举的并行执行多
个任务,没有更多的线程管理,酷!使用 dispatch_get_global_queue 函数检索一个并发队列的句柄。
Serial Queues
无论你提交同步或者异步任务,这些队列总是按照先入先出(FIFO)的原则来执行任务,这就意味着它
们一次执行一个 Block Object。然而,他们不在主线程上运行,所以对于那些要按照严格顺序执行并不阻塞主
线程的任务而言是一个完美的选择。使用 dispatch_queue_create 函数创建一个串行队列。一旦你使用完整队
列,必须使用 dispatch_release 函数释放它。
在 APP 生命周期内的任何时刻,你可以同时使用多个分派队列。你的系统只有一个主队列,但是你可以
创建多个串行队列来实现任何你需要 APP 实现的功能,当然是在合理的范围内。你也可以检索多个并发队列
并将任务分派给它们,任务可以通过 2 种方式传递分派队列:Block Objects 和 C 函数,这些我们会在 5.4 中详
细讲解。
Block Object 是通常在 Objective-C 中以方法形式出现的代码包。Block Objects 和 GCD 共同创建了一个和
谐环境,在这个环境里你能在 iOS 和 Mac OS X 上发布高性能的多线程 APPs。Block Objects 和 GCD 有什么特
别的地方呢?你可能会问。很简单:没有太多的线程!所有你要做的事情就是把代码放进 Block Objects 然后
要求 GCD 来为你小心执行代码。Objective-C 中的 Block Object 是编程领域所调用的一类的对象。这意味着你
可以动态生成的代码、把 Block Object 作为参数传递给方法,并从一种方法返回一个 Block Object。所有这些
事情能让你更容易地选择你想在运行库做什么,并且更容易改变一个程序的活动。特别的,Block Objects 能够
通过 GCD 在单个线程上运行。作为 Objective-C 对象,Block Objects 可以看成和其他任何对象一样。
Block Operations
它们促使执行一个或多个 Block Objects.Invocation Operations
这些允许你在另一个当前存在的对象中调用一个方法。
Plain Operations
这些都是需要被继承普通操作的类。将要执行的代码会被写入操作对象的主要方法中。
和前面提到的一样,Operations 由拥有 NSOperationQueue 数据类型的操作队列管理。再将后面提到的操作
类型(块、调用或普通操作)实例化之后,你可以把它们增加到一个操作队列中,并用这个队列来管理操作。
一个操作对象可以和其他操作对象有依存项,在执行与其相关任务之前必须等待一个或者多个操作完成。
除非你增加了依存项,否则你无法控制操作运行的顺序。例如,按照一定顺序把它们增加到一个队列上不能保
证它们按照这个顺序执行,尽管使用了队列选项。
在用操作队列和操作的过程中,有几个要点要记住:
默认情况下,操作使用启动实例方法在启动它们的线程上运行。如果你想让操作异步工作,必须使用
一个操作队列和一个类 NSOperation,在运行的主要实例方法上分离一个新的线程。
一个操作在开始它自己的运行之前可以等待另一个操作执行结束。但是要小心不要创建一个相互依存
操作,一个常见的错误就是争用条件。换句话说,如果 B 已经依赖了 A,不要让操作 A 依赖操作 B。
这将会导致双方永远等待下去,会占用内存甚至可能挂你的应用程序。
操作可以取消。因此,如果你由 NSOperation 子类来创建自定义操作对象,你必须确保使用 isCancelled
方法去检查这个操作在执行与其相关的任务之前是否已被取消掉。例如,如果操作的任务是每 20 秒
检查一次网络连接的可用性,必须在每个运行开始就调用 isCancelled 实例方法来保证在尝试再次检
查网络连接之前它已经被取消掉。如果操作多花了几秒钟(比如当你下载文件的时候),在运行任
务的时候你应该定期检查 isCancelled。
操作对象的 key-value observing (KVO)符合各种关键路径(如 isFinished 的 IsReady,isExecuting)。我
们将在后面的章节中讨论 Key Value Coding 和 Key Value Observing。
如果您计划将 NSOperation 子类化并提供自定义实现,必须在操作的主要方法中创建您自己的
autorelease 池,这个 autourelease 池从 start 方法获取调用。我们将在本章后面的内容中详细讨论这个
问题。
始终保持对创建的操作对象的引用。操作队列的并发性,使得它不可能在操作被添加到操作队列后还
会检索引用。
线程和定时器是继承 NSObject 的对象。线程比计时器要低一个级别。当 APP 在 iOS 上运行时,操作系统
为 APP 创建了至少一个线程,称作主线程,每个线程必须添加到一个运行循环中。运行循环,顾名思义,是
一个循环过程中的不同事件可以发生,如触发计时器或运行线程。关于运行循环的讨论超出了本章的范围,但
是我们在此处仍或多少提及。
将运行循环想象成一种有一个开始点、一个完成条件和一系列在其生命周期内发生的事件的循环。一个线
程或者计时器与一个运行附加到运行循环,事实上需要运行循环激活其功能。
一个 APP 的主线程是处理 UI 事件的线程。如果你在主线程执行一个长时间运行的任务,就要注意 APP
的 UI 会没有响应或者响应缓慢。为了避免这一点,你可以创建一个独立线程和/或计时器,它们会分别执行各
自的任务(即使是一个长时间运行的任务)同时又不会阻塞主线程。
简单总结关于 Block Objects 变量你必须了解的特点:
局部变量在 Block Objects 和 Objective-C 方法中的工作原理非常相似。
对于内联 Block Objects,局部变脸不仅包含 Block 内部定义的变量,并且包含在 Block Objects 执行方法
中定义的变量。(随后会有举例)
你不能参考 self;在 Objective-C 类中运行的独立 Block Objects,如果你需要访问 self, 就必须把 Object
作为参数传递到 BlockObject,我们很快会看到举例。
只有当 self 出现在创建 Block Object 的词法范围内,你可以在内联 Block Object 内参考 self。
对于内联 Block Objects,那些在 BlockObject 执行过程中定义的局部变量是可读写的,换句话说,对于
Block Objects 自身的局部变量来说,Block Objects 有个读写存取。
对于内联 Block Objects,实现 Object 的 Objective-C 方法的局部变量只能从中读取,不能写入。不过
还有一个例外,如果定义它们通过 __block 存储类型定义的话,Block Object 可以写入此类的变
量。对此我们也会有举例。
•假设你有一个类 NSObject 的 Object,并且在这个 Object 的执行中你使用了一个 Block Object 与 GCD
相连,那么在这个 Block Object 内部,你会有一个存储来读取那个支持你的 Block 执行的 NSObject
内部的声明属性。
只有当你使用声明属性的 setter and getter 方法你才能获取独立 Block Objects 内部的 NSObject 的这些属
性;在一个独立 Block Object 使用 Dot Notation 方法你无法获取一个 Object 的声明属性。