iOS Concurrency-Operation
发布日期:2022-03-18 08:27:39 浏览次数:30 分类:技术文章

本文共 11338 字,大约阅读时间需要 37 分钟。

推荐阅读

一些概念性的东西可参考

iOS Concurrency-Operation

Swift

在Swift中取代NSOperation,取代NSBlockOperation,取代NSOperationQueue

Operation状态的改变的流程如下:

1.isReady->isExecuting->isCancelled->isFinished

或者
2.isReady->isExecuting->isFinished

原版本

简介,参见NSOperation的文档

An operation object is a single-shot object—that is, it executes its task once and cannot be used to execute it again. You typically execute operations by adding them to an operation queue (an instance of the OperationQueue class). An operation queue executes its operations either directly, by running them on secondary threads, or indirectly using the libdispatch library (also known as Grand Central Dispatch). For more information about how queues execute operations, see OperationQueue.

文档建议把operation放入OperationQueue中

If you do not want to use an operation queue, you can execute an operation yourself by calling its start() method directly from your code. Executing operations manually does put more of a burden on your code, because starting an operation that is not in the ready state triggers an exception. The isReady property reports on the operation’s readiness.

如果不想使用operation queue,你可以执行operation ,通过调用start()方法。要确保operation在ready的state。

Asynchronous Versus Synchronous Operations

If you plan on executing an operation object manually, instead of adding it to a queue, you can design your operation to execute in a synchronous or asynchronous manner. Operation objects are synchronous by default. In a synchronous operation, the operation object does not create a separate thread on which to run its task. When you call the start() method of a synchronous operation directly from your code, the operation executes immediately in the current thread. By the time the start() method of such an object returns control to the caller, the task itself is complete.

如果想手动的执行一个operation对象,而不是把它添加到一个队列中,你可以设计你的operation是以同步或异步方式执行。Operation默认是synchronous,synchronous的operation并不会创建一个单独的thread ,当调用synchronous的operationstart()方法,operation在当前的thread中执行。当对象的start()方法返回控制给调用者的时候,任务本身就完成了。

When you call the start() method of an asynchronous operation, that method may return before the corresponding task is completed. An asynchronous operation object is responsible for scheduling its task on a separate thread. The operation could do that by starting a new thread directly, by calling an asynchronous method, or by submitting a block to a dispatch queue for execution. It does not actually matter if the operation is ongoing when control returns to the caller, only that it could be ongoing.

调用asynchronous的operation的start()方法,在对应的task完成之前,就返回了。异步的operation对象负责在单独的线程上调度其任务。operation可以通过通过调用异步方法或将块提交到调度队列执行,来开启一个新的线程。

If you always plan to use queues to execute your operations, it is simpler to define them as synchronous. If you execute operations manually, though, you might want to define your operation objects as asynchronous. Defining an asynchronous operation requires more work, because you have to monitor the ongoing state of your task and report changes in that state using KVO notifications. But defining asynchronous operations is useful in cases where you want to ensure that a manually executed operation does not block the calling thread.

如果你想使用queue执行operation,把它们定义为synchronous会更简单点。如果想手动的执行operation,你可能会想把operation作为asynchronous。作为asynchronous,需要做更多的操作,原因是你需要监控task的状态,通过KVO来告知状态的改变。但是,如果想要确保手动执行的operation不会阻塞当前调用的线程,则定义异步operation是非常有用的

When you add an operation to an operation queue, the queue ignores the value of the isAsynchronous property and always calls the start() method from a separate thread. Therefore, if you always run operations by adding them to an operation queue, there is no reason to make them asynchronous.

当把operation添加到operation 队列中,queue会忽略isAsynchronous属性,并总是在一个单独的线程中调用start()方法。因此,如果你始终将operation添加到操作队列来运行operation,则没有理由使operation为异步。

Subclassing Notes

对于non-concurrentoperation,你只需要重写

Into this method, you place the code needed to perform the given task. Of course, you should also define a custom initialization method to make it easier to create instances of your custom class. You might also want to define getter and setter methods to access the data from the operation. However, if you do define custom getter and setter methods, you must make sure those methods can be called safely from multiple threads.

如果你要创建的是concurrent operation,需要重写如下的方法和属性:

NSOperation concurrent vs non-concurrent

参考

NSOperationQueue always executes operations concurrently, while taking dependencies into account.

A “non-concurrent” operation requires a separate thread in order to execute concurrently. NSOperationQueue is responsible for providing this thread. In other words, a non-concurrent operation depends on NSOperationQueue to make it a concurrent operation.

A “concurrent” operation is concurrent on its own; it doesn’t need NSOperationQueue to create a thread for it. An example would be an operation that uses asynchronous file IO.

If you want two or more operations to execute serially you need to use dependencies.

If you want an operation to block the main thread then don’t use NSOperationQueue; just run each operation one after the other on the main thread.

To manually set maximum of concurrent operations, use method on operationQueue setMaxConcurrentOperationCount:

NSOperation

NSOperation表示了一个独立的计算单元。NSOperation有不同的状态,isReady → isExecuting → isFinishedisCancelled,具体请参考

NSBlockOperationNSOperation的子类

如下创建NSBlockOperation,在block中对两个数做加法

let summationOperation = BlockOperation {     result = 2 + 3    sleep(3)}duration {    summationOperation.start()}result

duration用来计算运行时间,定义如下:

public func duration(_ block: () -> ()) -> TimeInterval {  let startTime = Date()  block()  return Date().timeIntervalSince(startTime)}

如上,输出的当期的线程是<NSThread: 0x7fc610f06570>{number = 1, name = main}duration时间大概是3s

所以上述的方法是在当前的线程中执行,并没有异步的执行

管理一个或多个block的并发执行,只有当所有的block都完成执行,operation才被认为finished

Blocks added to a block operation are dispatched with default priority to an appropriate work queue

添加到block operation的blocks将以默认优先级分派到适当的工作队列上

let multiPrinter = BlockOperation()multiPrinter.addExecutionBlock { print("Hello"); sleep(2) }multiPrinter.addExecutionBlock { print("my"); sleep(2) }multiPrinter.addExecutionBlock { print("name"); sleep(2) }multiPrinter.addExecutionBlock { print("is"); sleep(2) }multiPrinter.addExecutionBlock { print("wz"); sleep(2) }duration {    multiPrinter.start()}

观察输出结果,可以发现控制台输出只用了4秒,而且并不能保证输出的顺序,运行在当前的线程。

还可以给BlockOperation添加一个block完成后的回调:

multiPrinter.completionBlock = {    print("Done")}

继承NSOperation

通过继承NSOperation,可以更精确的控制operation

如下的例子,是给图片添加blur的效果,TiltShiftOperation继承自Operation,重写了其main方法

class TiltShiftOperation: Operation{
var inputImage: UIImage? var outputImage: UIImage? override func main() { //处理图片 outputImage = tiltShift(image: inputImage) }}

使用方式如下:

let tsOp = TiltShiftOperation()tsOp.inputImage = inputImageduration {    tsOp.start()}

给图片添加blur效果,大概花费了1.08s,而且是在当前的线程中执行的。

NSOperationQueue

NSOperationQueue is responsible for scheduling and running a set of operations, somewhere in the background.

  • class var main: OperationQueue获取main队里
  • maxConcurrentOperationCount属性来控制并发任务的数量,为1的话相当于串行队列
  • cancelAllOperations()取消operation
  • waitUntilAllOperationsAreFinished()会block当前的线程

操作可以当做闭包直接加入到queue中

let printerQueue = OperationQueue()duration {    printerQueue.addOperation { sleep(3); print("Hello") }    printerQueue.addOperation { sleep(3); print("my") }    printerQueue.addOperation { sleep(3); print("name") }    printerQueue.addOperation { sleep(3); print("is") }    printerQueue.addOperation { sleep(3); print("sam") }}

上述的代码并不能保证输出的顺序,duration大概为0.001s

也可以添加多个Operation到queue中,已上面的图片处理Operation为例

let filterQueue = OperationQueue()//串行队列let appendQueue = OperationQueue()appendQueue.maxConcurrentOperationCount = 1for image in images {    let filterOp = TiltShiftOperation()    filterOp.inputImage = image    filterOp.completionBlock = {        guard let output = filterOp.outputImage else { return }        //添加处理完的图片到filteredImages中        appendQueue.addOperation({             filteredImages.append(output)        })    }    filterQueue.addOperation(filterOp)}

另外参考

暂停和继续queue

如果你想临时暂停Operations的执行,可以使用queue的setSuspended:方法暂停queue。不过暂停一个queue不会导致正在执行的operation在任务中途暂停,只是简单地阻止调度新Operation执行。你可以在响应用户请求时,暂停一个queue来暂停等待中的任务。稍后根据用户的请求,可以再次调用setSuspended:方法继续queue中operation的执行

// 暂停queue  [queue setSuspended:YES];  // 继续queue  [queue setSuspended:NO];

Asynchronous Operations

在上面的例子中,讲的都是sync类型的task,使用当前的线程来执行任务,直至task完成才return

async类型的方法却是在task完成之前,就直接return了,一个常见的场景是网络请求

1.最开始OperationisReadyisExecutingisFinished都被设置为false

2.之后isReady变为true
3.start()方法后,isExecuting变为true
4.异步方法调用结束后,设置isFinishedtrueisExecuting设置为false

Operation类依赖KVO来发送state的通知:

var state = State.Ready {    willSet {        willChangeValueForKey(newValue.keyPath)        willChangeValueForKey(state.keyPath)    }    didSet {        didChangeValueForKey(oldValue.keyPath)        didChangeValueForKey(state.keyPath)    }}

为了简化代码,先创建一个AsyncOperation基类,AsyncOperation继承自Operation,来自动的处理state的改变,如下:

1.operation的state是只读,所以使用自定义的state

2.asynchronous设置为true,告知系统手动管理state
3.重写isReadyisExecutingisFinished方法
4.重写start()方法,更新状态
5.重写cancel()方法

class AsyncOperation: Operation {
//State枚举 enum State: String { case Ready, Executing, Finished fileprivate var keyPath: String { return "is" + rawValue } } var state = State.Ready { willSet { willChangeValue(forKey: newValue.keyPath) willChangeValue(forKey: state.keyPath) } didSet { didChangeValue(forKey: oldValue.keyPath) didChangeValue(forKey: state.keyPath) } }}extension AsyncOperation { override var isReady: Bool { return super.isReady && state == .Ready } override var isExecuting: Bool { return state == .Executing } override var isFinished: Bool { return state == .Finished } override var isAsynchronous: Bool { return true } override func start() { if isCancelled { state == .Finished return } main() state == .Executing } override func cancel() { super.cancel() state == .Finished }}

所以,在AsyncOperation的基础上,包含一个异步函数的步骤如下:

1.继承AsyncOperation

2.重写main()方法,调用异步方法
3.在异步的回调中改变state状态为.Finished

如,有一个异步的网络下载。如下,模拟一个异步的图片下载:

public func simulateNetworkLoadImage(named: String?) -> UIImage? {  sleep(1)  guard let named = named else { return .none }  return UIImage(named: named)}public func simulateAsyncNetworkLoadImage(named: String?, callback: @escaping (UIImage?) -> ()) {  //异步下载图片  OperationQueue().addOperation {    let image = simulateNetworkLoadImage(named: named)    callback(image)  }}

创建一个异步下载的ImageLoadOperation,继承自AsyncOperation

class ImageLoadOperation: AsyncOperation {
//输出图片的名称 var inputName: String? //下载的图片 var outputImage: UIImage? override func main() { simulateAsyncNetworkLoadImage(named: self.inputName) { [unowned self] (image) in self.outputImage = image self.state = .Finished } }}

使用方式如下,创建一个ImageLoadOperation的实例,把它加入到OperationQueue中即可:

let queue = OperationQueue()let imageLoad = ImageLoadOperation()imageLoad.inputName = "train_dusk.jpg"queue.addOperation(imageLoad)

运用

参考

转载地址:https://windzen.blog.csdn.net/article/details/52748014 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:iOS Views要点
下一篇:iOS Concurrency-依赖、取消任务

发表评论

最新留言

感谢大佬
[***.8.128.20]2024年03月16日 09时27分43秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

hb100 微波雷达arduino_HB100微波雷达模块无线X波段雷达探测器探头传感器模块10.525GHz... 2019-04-21
go sqlite mysql_Go语言中使用SQLite数据库 2019-04-21
jmeter提取mysql数据_利用Jmeter操作MySQL数据库 2019-04-21
linux配置php mysql_Linux下php+mysql+nginx编译搭建(一) 2019-04-21
子查询和关联查询的优缺点_Part12:SQL复杂查询(视图、子查询、标量子查询、关联子查询)+校园成绩查询... 2019-04-21
mysql终端数据表操作_MySQL之终端(Terminal)管理数据库、数据表、数据的基本操作(转)... 2019-04-21
mysql dump到远程机器_mysqldump 从远程机器通过慢速网络复制数据库 2019-04-21
求偏导c语言,求偏导是什么?有什么用法?请举例说明。 2019-04-21
android drawtext 方法,关于Android Canvas.drawText方法中的坐标参数的正确解释 2019-04-21
android技术控,技术控必看,专业人士细谈USB Type-C 2019-04-21
html写计算机中的指数,计算机基础快速测验100题 2019-04-21
html执行父页面的方法,JQuery iframe页面通过parent方法操作父页面中的元素与方法(实例讲解)... 2019-04-21
pythondjangoweb典型模块开发实战 pdf下载_胡阳《Django企业开发实战高效Python Web框架指南》PDF及代码... 2019-04-21
分包组包 北斗通信_一种基于北斗的低功耗双向非实时通信方法 2019-04-21
c中tabpage控件上显示的文本_winform tabcontrol控件的标签放到左边后,文字成是上下显示的... 2019-04-21
hdfs合并块_HDFS架构与原理详解 2019-04-21
西门子stl语言编写教程_21天学通C++语言 视频教程 (第6六版) 编程入门基础 (共69讲)... 2019-04-21
ip设计包括什么_如何选择动漫设计培训学校?课程大纲包括什么专业? 2019-04-21
db2查询字段备注_MacOS上的数据库查询工具哪个好用? 2019-04-21
.net如何获取方法返回的类型_如何获取蜂群?如何养殖?老蜂农:3种方法获取,2种方法养殖... 2019-04-21