NS for iOS Devs —并发

在Swift Post上以更好的格式阅读此文章。

在专注于iOS中的Application Lifecycle和View Lifecyle之后,我们的下一个主题是concurrency 。 在当今世界,我们面临着同时处理多个操作的挑战。 我们希望缩短用户的等待时间,即使这并非总是必要的。 长时间显示负载或冻结UI会转移用户的注意力,并会不时减少应用程序的使用。 同时,保持注意力在应用程序中变得非常困难。 因此,我们不必并行运行操作,而必须并行运行它们,以获得更好的用户体验。

为什么我们需要非常了解并发性?

如今,几乎每个应用程序都可以进行在线数据传输(网络请求和响应)。 正确处理网络操作非常重要。 除了应用程序功能之外,我们不想在使用应用程序时打扰或阻止用户。 中断总是会导致不良的用户体验。 因此,当我们显示任何类型的UI时,我们通常同时处理网络请求。 我们可以在显示加载动画的同时发送网络请求并等待响应。 并发由iOS中的不同API处理。 我们将重点介绍基本的Grand Central Dispatch(GCD),以了解并发性。

在编程世界中,并发是一个大话题,并且具有至关重要的地位。 如果您想了解有关并发的更多信息,有关Quora的答案很好解释。

在谈论DispatchQueue之前,我们应该首先了解队列。 因为DispatchQueue可以在不同的队列上运行操作。

我们到底需要了解什么队列?

iOS中有两种类型的队列,即主队列和其他队列。 我们只有一个主队列,但是我们可以有多个后台队列,它们可以在其上运行不同的操作。 后台队列根据其优先级在线程池上运行。 但是主队列在主线程上运行。 所有UI(用户界面)更新都必须在主线程上运行。 因此,我们将主队列用于UI。

最重要的是这里只有一个主队列 。 主队列应合理使用。 由于每个UI更新都必须在主队列中完成,因此,如果我们对其他每个操作都使用主队列,则可以冻结UI操作。 例如,如果我们在主队列上发送网络请求并根据响应更新用户界面,我们将看到用户界面直到返回数据才响应。 提示是我们应该尽可能多地使用后台队列,并尝试将主队列留空用于非UI操作。

我们如何使用带有简单网络请求和GCD的队列?

发送网络请求非常简单。 URLSession是用于简单网络请求的最常用方法。 我们可以通过dataTask发送请求,并在闭包中获取带有错误(如果有)的响应数据。

最后,我们可以看到具有更好用户体验处理的​​示例代码。 下面的代码从后台队列开始,当需要在屏幕上显示加载指示器时,我们切换到主队列。 执行继续并启动任务。 收到响应后,我们仍在后台队列中。 如果出现错误,现在我们将切换到主队列并显示警报。

主线程与主队列以及它们为什么不相同可能会引起混淆。 有一个主线程和一个主队列。 如果您对更多细节感到好奇,可以阅读这篇文章。

我们已经讨论了一个简单的网络请求和GCD。 对于许多应用程序而言,同时处理更复杂的任务很重要,而GCD仅提供一种简单而直接的解决方案。 我们可能需要取消并发操作或遵循其状态。 GCD没有提供解决这些问题的方法。 这就是为什么我们将从OperationOperationQueue获得帮助。 OperationOperationQueue链接,它们也可以相互依赖。 例如,如果我们要在获取餐厅食物清单后下载食物图像,则应将获取操作添加为下载操作的依赖项,并将其放入同一OperationQueue 。 因此,下载操作将等待提取操作完成。

让我们看一下基本的Operation示例:

与其讨论过多有关OperationOperationQueue细节,不如让我们看一下我们应该知道的重要事项:

  • 操作对象是单发对象 。 这是不可重用的。 当我们使用操作对象时,基本上就完成了。 如果要重复相同的操作,则需要从自定义操作类中创建另一个实例。
  • Operation是与单个任务相关联的抽象类。 因此,我们无法创建Operation新实例。 相反,我们可以像示例中那样将其子类化,也可以使用系统范围内定义的子类(例如BlockOperation )。 如果操作具有依赖项,则在所有依赖项完成执行之前,它不会被视为就绪。 当最后一个依赖项完成时,该操作开始执行。
  • Operation符合KVC和KVO(我们将在后面讨论)。 因此,我们可以通过附加观察者来观察操作的变化。 但是我们不应该将操作绑定到UI元素。 因为UI元素必须在主线程上执行。
  • 从多个线程调用Operation的方法是安全的,但我们应注意重写方法和自定义实现中的线程安全性。
  • 如果我们要创建一个非并发操作,则仅重写main()方法就足够了。 但是对于并发操作,我们需要至少覆盖start()isAsynchronousisExecutingisFinished 。 在示例中,我们有一个并发操作。 这就是为什么我们用数字2注释所有覆盖的方法和属性的原因。 但是我们没有重写start()方法。 原因是start() main()方法在被调用时会自动调用main()方法。 覆盖isExecutingisFinished方法时,我们必须在操作完成或取消时生成KVO通知。 在示例中,我们可以看到这些部分以及带有数字1的注释。
  • 如果我们重写start()方法,则永远不要在自定义实现中调用super

如果我们想保持应用程序的响应速度并轻松地并行运行多个进程,则必须了解并发性并根据需要使用我们的工具。 GCD使我们变得简单。 但是,当我们想要进行高级和自定义操作(例如取消执行或将代码的某些部分重用于并发操作)时,这仅仅是不够的。 在这些情况下,使用OperationOperationQueue会减轻痛苦。 GCD和Operation是不同的东西。 两者的便捷使用对于创建健壮且用户友好的iOS应用至关重要。

在处理并发流程时,您遵循哪些策略? 您如何看待GCD的简单解决方案,以及如何使用它? 您是否曾经遇到过一个复杂的问题,您可以在Operation的帮助下迅速地解决问题? 让我知道您对Twitter @candostEN或下面的评论的想法,评论或反馈。


NS for iOS Devs Series的所有帖子

  • 应用生命周期
  • 查看生命周期
  • 并发
  • 可测性

进一步:

Apple派遣文件

苹果操作文档

了解有关并发,异步与同步的更多信息

在大中央调度区深潜

NSHipster — NSOperation

Swift中的Operation和OperationQueue教程

了解Swift中的操作和操作队列