了解Swift调度器

243 阅读4分钟

iOS应用开发中最常见的错误之一是线程错误,当开发者试图从一个闭包中更新用户界面时,会出现这种错误。为了解决这个问题,我们可以使用DispatchQueue.mainthreads

在本教程中,我们将学习什么是调度器,以及我们如何在iOS应用开发中使用它们来管理队列和循环。之前对Swift、Combine框架和iOS开发的知识是必要的。

让我们开始吧!

什么是调度器?

根据调度器的文档,调度器是 "一个定义何时何地执行一个闭包的协议"。从本质上讲,调度器为开发者提供了一种在特定安排下执行代码的方式,有助于在应用程序中运行队列命令。

开发人员可以通过使用调度器将大批量的操作迁移到二级队列中,释放出应用程序主队列的空间,并更新应用程序的用户界面。

调度器还可以优化并行执行命令的代码,允许开发者在同一时间执行更多的命令。如果代码是串行的,开发者可以一次执行一个位的代码。

调度器的类型

有几种类型的调度器是Combine内置的。值得注意的是,调度器遵循调度器协议,这可以在上面链接的调度器文档中找到。

让我们看一下几个流行的调度器

OperationQueue

根据其文件,一个 OperationQueue会根据命令的优先级和准备程度来执行命令。一旦你把一个操作添加到队列中,该操作将保持在其队列中,直到它完成执行其命令。

一个OperationQueue ,可以以串行或并行的方式执行任务,这取决于任务本身。OperationQueue 主要用于后台任务,如更新应用程序的用户界面。

DispatchQueue

苹果公司的文档将一个 [DispatchQueue](https://developer.apple.com/documentation/dispatch/dispatchqueue#:~:text=Dispatch%20queues%20are%20FIFO%20queues,tasks%20either%20serially%20or%20concurrently.&text=When%20you%20schedule%20a%20work%20item%20asynchronously%2C%20your%20code%20continues,the%20work%20item%20runs%20elsewhere.)是一个先入先出的队列,它可以接受块对象形式的任务,并以串行或并发的方式执行它们。

系统会在一个线程池上管理提交给DispatchQueue 的工作。除非DispatchQueue 代表一个应用程序的主线程,否则DispatchQueue 并不保证它将使用哪个线程来执行一个任务。

DispatchQueue 经常被认为是调度命令的最安全方式之一。然而,不建议在Xcode 11中使用DispatchQueue 。如果你在Xcode 11中使用DispatchQueue 作为调度器,它必须是串行的,以遵守Combine的操作符的契约。

ImmediateScheduler

一个 [ImmediateScheduler](https://developer.apple.com/documentation/combine/immediatescheduler)用来立即执行异步操作。

import Combine

let immediateScheduler = ImmediateScheduler.shared

  let aNum = [1, 2, 3].publisher
        .receive(on: immediateScheduler)
        .sink(receiveValue: {
       print("Received \$0) on thread \(Threa.currentT")t
})

例如,上面的代码块将发送一个类似于下面的代码块的输出。

Received 1 on thread <NSThread: 0x400005c480>{number = 1, name = main}
Received 2 on thread <NSThread: 0x400005c480>{number = 1, name = main}
Received 3 on thread <NSThread: 0x400005c480>{number = 1, name = main}

ImmediateScheduler 在应用程序的当前线程上立即执行命令。上面的代码块是在主线程上运行的。

RunLoop

RunLoop 调度器用于在一个特定的运行循环上执行任务。在运行循环上的行动可能是不安全的,因为RunLoops 不是线程安全的。因此,使用DispatchQueue 是一个更好的选择。

默认的调度器

如果你没有为一个任务指定调度器,Combine会为它提供一个默认的调度器。所提供的调度器将使用执行该任务的同一线程。例如,如果你执行一个UI任务,Combine提供的调度器会在同一个UI线程上接收该任务。

切换调度器

在使用Combine的iOS开发中,许多消耗资源的任务都是在后台完成的,以防止应用程序的UI冻结或完全崩溃。然后,Combine切换调度器,使任务的结果在主线程上执行。

Combine使用两种内置方法来切换调度器:receive(on)subscribe(on)

receive(on)

receive(on) 方法用于在一个特定的调度器上发出数值。它为任何在它被声明后的发布者改变一个调度器,如下面的代码块所示。

Just(3)
   .map { _ in print(Thread.isMainThread) }
   .receive(on: DispatchQueue.global())
   .map { print(Thread.isMainThread) }
   .sink { print(Thread.isMainThread) }

上面的代码块将打印出以下结果。

true 
false 
false 

subscribe(on)

subscribe(on) 方法被用来在一个特定的调度器上创建一个订阅。

import Combine 
print("Current thread \(Thread.current)")
let k = [a, b, c, d, e].publisher
    .subscribe(on: aQueue)
    .sick(receiveValue: {
        print(" got \($0) on thread \(Thread.current)")
  })

上面的代码块将打印出以下结果。

Current thread <NSThread: 0x400005c480>{number = 1, name = main}
Received a on thread <NSThread: 0x400005c480>{number = 7, name = null}
Received b on thread <NSThread: 0x400005c480>{number = 7, name = null}
Received c on thread <NSThread: 0x400005c480>{number = 7, name = null}
Received d on thread <NSThread: 0x400005c480>{number = 7, name = null}
Received e on thread <NSThread: 0x400005c480>{number = 7, name = null}

在上面的代码块中,这些值是从不同的线程而不是主线程发出的。subscribe(on) 方法串行地执行任务,从执行指令的顺序可以看出。

用调度器执行异步任务

在本节中,我们将学习如何在subscribe(on)receive(on) 调度器方法之间进行切换。想象一下,一个发布者正在后台运行一个任务。

struct BackgroundPublisher: Publisher
  typealias Output = Int
  typealias Failure = Never 

  func receive<K>(subscriber: K) where K : Subcriber, Failure == K.Failure, Output == K.Input {
  sleep(12) 
  subscriber. receive(subscriptiton: Subscriptions.empty)
  _= subscriber.receive(3)
  subscriber.receive(completion: finished)
}

如果我们从一个用户界面线程中调用该任务,我们的应用程序将冻结12秒。Combine将在我们任务执行的同一个调度器中添加一个默认的调度器。

BackgroundPublisher()
    .sink { _ in print("value received") }

print("Hi!")

在上面的代码块中,Hi! ,在接收到数值后,会在我们的控制台中打印出来。我们可以看到下面的结果。

value received
Hi!

在Combine中,这种类型的异步工作经常通过在后台调度器上订阅和在用户界面调度器上接收事件来执行。

BackgroundPublisher()
    .subscribe(on: DispatchQueue.global())
    .receive(on: DispatchQueue.main)
    .sink { _ in print("Value recieved") }

print("Hi Again!")

上面的代码片断将打印出下面的结果。

Hi Again!
Value received

Hi Again! ,在接收到数值之前被打印出来。现在,发布者不会因为阻塞我们的主线程而冻结我们的应用程序。

总结

在这篇文章中,我们回顾了什么是调度器以及它们如何在iOS应用程序中工作。我们介绍了一些最佳的使用案例,包括OperationQueue,DispatchQueue,ImmediateScheduler, 和RunLoop 。我们还谈到了Combine框架以及它是如何影响Swift中调度器的使用。

我们学习了如何在Swift中使用receive(on)subscribe(on) 方法来切换调度器。我们还学习了如何在Combine中使用调度器执行异步功能,即在后台调度器上订阅并在用户界面调度器上接收我们的值。

The postUnderstanding Swift schedulersappeared first onLogRocket Blog.