iOS Operation 的整理

operation

(上图是一些 Operation 经常使用的功能的UML图)swift

1. 为何使用 Operation & OperationQueue

GCD 是在封装了C,进行多线程开发。而 Operation & OperationQueue 则是封装了 GCD 进行多线程开发。在平时用GCD就能解决不少多线程任务开发的问题, 那为何还要使用 Operation & OperationQueue 呢?安全

使用 Operation & OperationQueue 可以:bash

  • 设置 Operation 之间的依赖;
  • 经过 KVO 观察 Operation 的执行状态;
  • 更加方便的设置 Operation 优先级
  • OperationQueue 能够更方便的取消全部在队列里面的 Operation
  • 使用 OperationQueue 更方便的执行 Operation 的暂停和恢复执行(使用 isSuspend)

2. Operation & OperationQueue 介绍

Operation 一个操做,能够当作一个任务。相似于 GCD 里面的 DispatchItem。Operation 是一个抽象类,里面定义了任务安全执行的逻辑。 在实际使用的时候通常使用系统建立的子类 BlockOperation (Objective-C 还可使用 NSInvocationOperation)。或者建立 Operation 的自定义子类。多线程

OperationQueue 操做的队列,管理操做的执行,相似于 GCD 里面的 DispatchQueue。在 GCD 中,任务的执行顺序是先进先出 FIFO。OperationQueue 执行 Operation 的时候依据 Operation 的优先级和是否在准备就绪的状态。当一个 Operation 加入 OperationQueue 之后,只有等到 Operation 执行完毕(任务取消也是执行完毕)才能从 OperationQueue 里面移除。OperationQueue 不能直接把 Operation 移除。并发

3. Operation

增长一个辅助方法输出当前内容和执行的线程app

func printOperation<T>(_ message: T) {
    print(" \(message), thread = \(Thread.current)")
}
复制代码

3.1 建立 Operation 子类 BlockOperation

let operation = BlockOperation {
            printOperation("Op init block task 1 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Op init block task 1 end")
        }
        operation.addExecutionBlock {
            printOperation("Op task 2 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Op task 2 end")
        }
        
        operation.addExecutionBlock {
            printOperation("Op task 3 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Op task 3 end")
        }
复制代码

初始化一个 BlockOperation, 随后能够加入多个任务异步

  • 须要注意的是不要在 Operation 执行中或者执行完毕加入,不然会报错。
  • 多个任务添加的效果是任务会并发执行:若是本身调用 start(), BlockOperation { // task } 会在当前线程执行;addExecutionBlock { // task } 开启新线程执行

3.2 建立 Operation 自定义子类

能够定义本身的同步执行子类,和异步执行子类。同步执行子类的建立须要配置的比较少,通常状况只须要建立一个本身的初始化方法和覆盖 main() 方法就行了。异步执行的则比较复杂,须要本身设置多个状态属性。async

class CustomOperation: Operation {
    
    private let address: String
    
    init(address: String) {
        self.address = address
    }

    override func main() {
        if isCancelled {
            return
        }
        printOperation("CustomOperation task 1 begin")
        Thread.sleep(forTimeInterval: 3)
        printOperation("CustomOperation task 1 end")
    }
}
复制代码
  • 须要注意的是的是覆盖的 main() 方法须要判断任务是否取消。
  • isCancelled 的判断处通常是任务刚开始以及一些耗时任务的开始执行与完毕的添加

具体可参考:Apple Operation Documentationide

3.2 定制 Opearation 行为

3.2.1 添加依赖

let operationTask2 = BlockOperation {
            printOperation("Operation task 2 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 2 end")
        }
        let operationTask3 = BlockOperation {
            printOperation("Operation task 3 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 3 end")
        }
        
        let operationTask4 = BlockOperation {
            printOperation("Operation task 4 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 4 end")
        }

        // 2 -> 4 - > 3
        operationTask3.addDependency(operationTask4)
        operationTask4.addDependency(operationTask2)
复制代码

在上面的例子中,operationTask4 的开始执行依赖 operationTask2,operationTask3 的开始执行依赖 operationTask4。ui

  • 添加的依赖 Operation 能够在不一样的 OperationQueue;
  • 当 Operation 添加到 OperationQueue 之后就不要再设置依赖了,谁也不知道会发生什么;
  • Operation 的依赖添加注意不要产生循环。

3.2.2 修改 Operation 的优先级

let operationTask2 = BlockOperation {
            printOperation("Operation task 2 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 2 end")
        }
        let operationTask3 = BlockOperation {
            printOperation("Operation task 3 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 3 end")
        }
        
        let operationTask4 = BlockOperation {
            printOperation("Operation task 4 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 4 end")
        }

        operationTask2.queuePriority = .veryHigh
        operationTask3.queuePriority = .normal
        operationTask4.queuePriority = .low
复制代码

在上面设置了 Operation 的队列优先级属性

  • 设置 queuePriority 只会大几率先执行高优先级的,不必定高优先级的就必定先执行;
  • Operation 的实际执行是判断它是否准备就绪能够执行任务了,若是它还有依赖没有执行完毕,那么它确定不会执行的。若是多个 Operation 都是能够执行状态才会大几率先执行高优先级的 Operation。

3.2.3 设置 Operation completionBlock

operation.completionBlock = {
            DispatchQueue.main.async {
                // Task done
            }
        }
复制代码
  • 因为操做执行完毕的线程是不肯定的,要是有一些内容须要更新。最好是回到主线程更新
  • Operation 不论是正常执行完毕,仍是任务被取消了,都会调用 completionBlock

3.3 执行 Operation

和 GCD 里面的 DispatchWorkItem 同样,任务能够本身执行,也能够提交给队列执行。

3.3.1 本身执行

本身执行调用 start() 方法。下面看一些例子:

BlockOperation 的单个任务

private func singleBlockOperation() {
        printOperation("Test begin")
        
        let operation = BlockOperation {
            printOperation("Operation init block task 1 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation init block task 1 end")
        }
                
        printOperation("Operation start")
        operation.start()
        printOperation("Test end")
    }
复制代码

输出:

Test begin, thread = <NSThread: 0x6000039ead80>{number = 1, name = main}
 Operation start, thread = <NSThread: 0x6000039ead80>{number = 1, name = main}
 Operation init block task 1 begin, thread = <NSThread: 0x6000039ead80>{number = 1, name = main}
 Operation init block task 1 end, thread = <NSThread: 0x6000039ead80>{number = 1, name = main}
 Test end, thread = <NSThread: 0x6000039ead80>{number = 1, name = main}
复制代码

能够看到 start() 默认是一个同步执行方法。它会等待任务在当前线程执行完毕 (例子此时在主线程)。

  • 其实也能够设置为异步执行的,不过须要添加一些额外的操做,通常异步执行使用的状况是添加到 OperationQueue。由 OperationQueue 处理更多相关信息。

BlockOperation 的多个任务

状况一:

private func singleBlockMutipleTaskOperation() {
        printOperation("Test begin")
        
        let operation = BlockOperation {
            printOperation("Operation init block task 1 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation init block task 1 end")
        }
             
        operation.addExecutionBlock {
            printOperation("Operation task 2 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 2 end")
        }
        
        operation.addExecutionBlock {
            printOperation("Operation task 3 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 3 end")
        }

        printOperation("Operation start")
        operation.start()
        printOperation("Test end")
    }
复制代码

输出:

Test begin, thread = <NSThread: 0x6000006b6d00>{number = 1, name = main}
 Operation start, thread = <NSThread: 0x6000006b6d00>{number = 1, name = main}
 Operation init block task 1 begin, thread = <NSThread: 0x6000006b6d00>{number = 1, name = main}
 Operation task 2 begin, thread = <NSThread: 0x600000635d00>{number = 6, name = (null)}
 Operation task 3 begin, thread = <NSThread: 0x600000620e80>{number = 7, name = (null)}
 Operation task 3 end, thread = <NSThread: 0x600000620e80>{number = 7, name = (null)}
 Operation init block task 1 end, thread = <NSThread: 0x6000006b6d00>{number = 1, name = main}
 Operation task 2 end, thread = <NSThread: 0x600000635d00>{number = 6, name = (null)}
 Test end, thread = <NSThread: 0x6000006b6d00>{number = 1, name = main}
复制代码

此时 BlockOperation 第一个任务 BlockOperation { // task }在当前线程(当前线程是主线程)执行,额外的任务 addExecutionBlock { // task } 在其它线程执行。

状况二:

private func singleBlockMutipleTaskOperationAddTasks() {
        printOperation("Test begin")
        
        let operation = BlockOperation()
             
        operation.addExecutionBlock {
            printOperation("Operation task 2 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 2 end")
        }
        
        operation.addExecutionBlock {
            printOperation("Operation task 3 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 3 end")
        }

        printOperation("Operation start")
        operation.start()
        printOperation("Test end")
    }
复制代码

输出:

Test begin, thread = <NSThread: 0x6000009c95c0>{number = 1, name = main}
 Operation start, thread = <NSThread: 0x6000009c95c0>{number = 1, name = main}
 Operation task 2 begin, thread = <NSThread: 0x6000009c95c0>{number = 1, name = main}
 Operation task 3 begin, thread = <NSThread: 0x600000959f40>{number = 6, name = (null)}
 Operation task 3 end, thread = <NSThread: 0x600000959f40>{number = 6, name = (null)}
 Operation task 2 end, thread = <NSThread: 0x6000009c95c0>{number = 1, name = main}
 Test end, thread = <NSThread: 0x6000009c95c0>{number = 1, name = main}
复制代码

和状况一的结果相似: 第一个任务 addExecutionBlock { // task1 } 在当前线程(当前线程是主线程)执行,额外的任务 addExecutionBlock { // tasks } 在其它线程执行。

3.3.2 添加到 OperationQueue 执行

  1. 通常状况:
private func queueAddOperations() {
        printOperation("Test begin")
        let queue = OperationQueue()
        
        queue.addOperation {
            printOperation("Operation task 1 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 1 end")
        }
        let operationTask2 = BlockOperation {
            printOperation("Operation task 2 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 2 end")
        }
        let operationTask3 = BlockOperation {
            printOperation("Operation task 3 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 3 end")
        }

        queue.addOperation(operationTask2)
        queue.addOperation(operationTask3)
        
        printOperation("Test end")
    }
复制代码

输出:

Test begin, thread = <NSThread: 0x600002bc6cc0>{number = 1, name = main}
 Test end, thread = <NSThread: 0x600002bc6cc0>{number = 1, name = main}
 Operation task 1 begin, thread = <NSThread: 0x600002b51980>{number = 7, name = (null)}
 Operation task 2 begin, thread = <NSThread: 0x600002bb3400>{number = 8, name = (null)}
 Operation task 3 begin, thread = <NSThread: 0x600002bc3280>{number = 9, name = (null)}
 Operation task 2 end, thread = <NSThread: 0x600002bb3400>{number = 8, name = (null)}
 Operation task 3 end, thread = <NSThread: 0x600002bc3280>{number = 9, name = (null)}
 Operation task 1 end, thread = <NSThread: 0x600002b51980>{number = 7, name = (null)}
复制代码

异步并不是执行而且全部任务开启新线程。

  1. 指定 OperationQueue 像串行队列同样运行:

OperationQueue 里面有一个设置当前队列最大操做并发执行的属性 maxConcurrentOperationCount

private func queueActAsSerial() {
        printOperation("Test begin")
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1 // 设置最大运行数目
        
        queue.addOperation {
            printOperation("Operation task 1 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 1 end")
        }
        
        let operationTask2 = BlockOperation {
            printOperation("Operation task 2 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 2 end")
        }
        let operationTask3 = BlockOperation {
            printOperation("Operation task 3 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 3 end")
        }
        
        queue.addOperation(operationTask2)
        queue.addOperation(operationTask3)
        
        printOperation("Test end")
    }
复制代码

输出:

Test begin, thread = <NSThread: 0x600002b66d00>{number = 1, name = main}
 Test end, thread = <NSThread: 0x600002b66d00>{number = 1, name = main}
 Operation task 1 begin, thread = <NSThread: 0x600002b14080>{number = 3, name = (null)}
 Operation task 1 end, thread = <NSThread: 0x600002b14080>{number = 3, name = (null)}
 Operation task 2 begin, thread = <NSThread: 0x600002bf7a80>{number = 6, name = (null)}
 Operation task 2 end, thread = <NSThread: 0x600002bf7a80>{number = 6, name = (null)}
 Operation task 3 begin, thread = <NSThread: 0x600002bf7a80>{number = 6, name = (null)}
 Operation task 3 end, thread = <NSThread: 0x600002bf7a80>{number = 6, name = (null)}
复制代码

能够当作此时就像是串行队列同样,只能等上一个任务完成才能执行下一个任务。这个最大并发数目在执行的时候最好是用默认值。它会由系统给出一个最优的值。在实际的设置中若是设置为 maxConcurrentOperationCount = 1000 系统也是会根据实际状况取最优值的。

  1. 添加依赖和 completionBlock
private func operationDependenciesAndCompletionBlock() {
        printOperation("Test begin")
        let queue = OperationQueue()
        
        queue.addOperation {
            printOperation("Operation task 1 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 1 end")
        }
        
        let operationTask2 = BlockOperation {
            printOperation("Operation task 2 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 2 end")
        }
        let operationTask3 = BlockOperation {
            printOperation("Operation task 3 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 3 end")
        }
        
        let operationTask4 = BlockOperation {
            printOperation("Operation task 4 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 4 end")
        }
        
        // 2 -> 4 - > 3
        operationTask3.addDependency(operationTask4)
        operationTask4.addDependency(operationTask2)
        
        operationTask4.completionBlock = {
            DispatchQueue.main.async {
                printOperation("operationTask4 completion")
            }
        }
        
        queue.addOperation(operationTask2)
        queue.addOperation(operationTask3)
        queue.addOperation(operationTask4)
        
        printOperation("Test end")
    }
复制代码

输出:

Test begin, thread = <NSThread: 0x600000bbebc0>{number = 1, name = main}
 Test end, thread = <NSThread: 0x600000bbebc0>{number = 1, name = main}
 Operation task 1 begin, thread = <NSThread: 0x600000b34000>{number = 6, name = (null)}
 Operation task 2 begin, thread = <NSThread: 0x600000b2c440>{number = 7, name = (null)}
 Operation task 1 end, thread = <NSThread: 0x600000b34000>{number = 6, name = (null)}
 Operation task 2 end, thread = <NSThread: 0x600000b2c440>{number = 7, name = (null)}
 Operation task 4 begin, thread = <NSThread: 0x600000b2c440>{number = 7, name = (null)}
 Operation task 4 end, thread = <NSThread: 0x600000b2c440>{number = 7, name = (null)}
 Operation task 3 begin, thread = <NSThread: 0x600000b34000>{number = 6, name = (null)}
 operationTask4 completion, thread = <NSThread: 0x600000bbebc0>{number = 1, name = main}
 Operation task 3 end, thread = <NSThread: 0x600000b34000>{number = 6, name = (null)}
复制代码

能够看出:

  • 依赖关系为: operationTask2 执行完毕之后执行 operationTask4。operationTask4 执行完毕之后执行 operationTask3。
  • operationTask4 执行完毕会调用它的 completionBlock
  • 添加进 OperationQueue 的 Operation 只有在准备好了才会执行。好比若是有依赖项就必须得执行完毕才会变成可执行状态。
  • GCD 入队是 FIFO, OperationQueue 则不必定。
  1. 等待执行完成

queue.waitUntilAllOperationsAreFinished() 这个会阻塞当前线程,直到队列里面全部 Operation 执行完毕。

  • 最好不要在主线程调用这个方法,否则会影响用户体验;
  • 调用该方法的时候还能够继续向队列里面添加 Operation。
  1. Operation 的取消
private func queueCancel() {
        printOperation("Test begin")
        let queue = OperationQueue()
        
        queue.addOperation {
            printOperation("Operation task 1 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 1 end")
        }
        
        let operationTask2 = BlockOperation {
            printOperation("Operation task 2 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 2 end")
        }
        let operationTask3 = BlockOperation {
            printOperation("Operation task 3 begin")
            Thread.sleep(forTimeInterval: 3)
            printOperation("Operation task 3 end")
        }
                
        operationTask2.completionBlock = {
            DispatchQueue.main.async {
                printOperation("operationTask2 completion. isFinished = \(operationTask2.isFinished), isCancelled = \(operationTask2.isCancelled)")
            }
        }
        queue.addOperation(operationTask2)
        queue.addOperation(operationTask3)
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            printOperation("xxxx cancelAllOperations")
            queue.cancelAllOperations()
        }
        
        printOperation("Test end")
    }
复制代码

输出:

Test begin, thread = <NSThread: 0x600002d1ecc0>{number = 1, name = main}
 Operation task 1 begin, thread = <NSThread: 0x600002d86b00>{number = 6, name = (null)}
 Operation task 2 begin, thread = <NSThread: 0x600002d8ba40>{number = 7, name = (null)}
 Operation task 3 begin, thread = <NSThread: 0x600002d68680>{number = 5, name = (null)}
 Test end, thread = <NSThread: 0x600002d1ecc0>{number = 1, name = main}
 xxxx cancelAllOperations, thread = <NSThread: 0x600002d1ecc0>{number = 1, name = main}
 Operation task 2 end, thread = <NSThread: 0x600002d8ba40>{number = 7, name = (null)}
 Operation task 1 end, thread = <NSThread: 0x600002d86b00>{number = 6, name = (null)}
 Operation task 3 end, thread = <NSThread: 0x600002d68680>{number = 5, name = (null)}
 operationTask2 completion. isFinished = true, isCancelled = true, thread = <NSThread: 0x600002d1ecc0>{number = 1, name = main}
复制代码
  • 调用 queue.cancelAllOperations() 会遍历调用全部的 operation.cancel()。取消操做不会自动将它们从队列中删除,也不会中止当前正在执行的操做
  • 即便调用 cancel() 之后,仍是会调用 operation.completionBlock { // xxxx }。
  1. 暂停和恢复 OperationQueue

经过设置 queue.isSuspended = true // or false

  • 还没执行的 operarion 就不暂停不许备执行了;
  • 正在执行的 operarion 继续执行;
  • 后加入的 operarion 暂停不许备执行。
  1. 一个资源竞争的使用例子

有时候一些关键资源须要保证线程安全,在 GCD 中可使用 DispatchSemaphore 使用,下面添加一个使用 NSLock 的例子

private var tickets = 12; // 12 张票
    private var lock: NSLock!

    private func queueWithThreadSafe() {
        lock = NSLock()
        
        let queue0 = OperationQueue()
        let queue1 = OperationQueue()

        // 添加画不一样队列的不一样操做
        queue0.addOperation {
            self.tikcetsSell()
        }
        
        queue0.addOperation {
            self.tikcetsSell()
        }

        queue0.addOperation {
            self.tikcetsSell()
        }
        
        queue1.addOperation {
            self.tikcetsSell()
        }
    }
    
    // 多个线程卖票,须要保证线程安全
    private func tikcetsSell() {
        while true {
            lock?.lock() // 加锁 ..... 在须要保存线程安全的代码前加锁
            if tickets > 0 {
                Thread.sleep(forTimeInterval: 0.3)
                tickets -= 1
                printOperation("Tickets count = \(tickets)")
            }
            lock?.unlock() // 去锁 ..... 在须要保存线程安全的代码后面去锁

            if tickets <= 0 {
                printOperation("Tickets sold out")
                break
            }
        }
    }

复制代码

输出:

Tickets count = 11, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
 Tickets count = 10, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
 Tickets count = 9, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
 Tickets count = 8, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
 Tickets count = 7, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
 Tickets count = 6, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
 Tickets count = 5, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
 Tickets count = 4, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
 Tickets count = 3, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
 Tickets count = 2, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
 Tickets count = 1, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
 Tickets count = 0, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
 Tickets sold out, thread = <NSThread: 0x60000145be80>{number = 7, name = (null)}
 Tickets sold out, thread = <NSThread: 0x6000014ba400>{number = 8, name = (null)}
 Tickets sold out, thread = <NSThread: 0x6000014b7540>{number = 9, name = (null)}
 Tickets sold out, thread = <NSThread: 0x6000014a8440>{number = 10, name = (null)}
复制代码

可使用 NSLock 在 OperationQueue 里面实现线程安全。

4. 最后

这里有一个自定义 Operaion 的实际例子:Raywenderlich: Operation and OperationQueue Tutorial in Swift

相关文章
相关标签/搜索