iOS 多线程开发之系列文章

iOS 多线程开发之概念

iOS 多线程开发之 Thread

iOS 多线程开发之 GCD

iOS 多线程开发之 Operation

iOS 多线程开发之线程安全

多线程开发是日常开发使命中不可短少的一部分,在 iOS 开发中常用到的多线程开发技能有 GCD、OperationThread,本文首要讲解多线系列文章中关于 Operation 的相关常识和运用详解。

简介

Operation 是苹果公司供给的一套完好的多线程处理方案,实际上它是基于 GCD 更高一层的封装,完全面向目标。相关于 GCD 而言运用更加的简单、代码更具可读性。包括网络请求、图片紧缩在内的诸多多线程使命事例都很好的运用了 Operation。当然 Operation 还需求 OperationQueue 这一重要角色配合运用。其间 Operation 是个抽象类,运用它有必要用它的子类,能够完结它或者运用它界说好的子类:BlockOperation。创立 Operation 子类的目标,把目标增加到 OperationQueue 行列里履行。

BlockOperation

单使命

let op = BlockOperation{
    print("单使命:(Thread.current)")
}
op.start()

运转成果:

单使命:<NSThread: 0x6000008e03c0>{number = 1, name = main}
let op = BlockOperation()
op.addExecutionBlock {
    print("使命一:(Thread.current)")
}
op.start()

运转成果:

使命一:<NSThread: 0x600002e28080>{number = 1, name = main}

总结:
在主线程中独自运用 BlockOperation 履行一个操作的情况下,操作是在当时线程履行的,并没有敞开新线程。

多使命

private func testOperationBlock() {
  let op = BlockOperation()
  ///使命一
  op.addExecutionBlock {
    print("使命一:\(Thread.current)")
  }
  ///使命二
  op.addExecutionBlock {
    print("使命二:\(Thread.current)")
  }
  ///使命三
  op.addExecutionBlock {
    print("使命三:\(Thread.current)")
  }
  op.start()
  print("结束")
}

成果:

使命二:<NSThread: 0x600003ee8900>{number = 6, name = (null)}
使命一:<NSThread: 0x600003ead6c0>{number = 7, name = (null)}
使命三:<NSThread: 0x600003ea4900>{number = 1, name = main}
结束

总结:

  1. BlockOperation还供给了一个办法addExecutionBlock,经过addExecutionBlock:就能够为 BlockOperation增加额定的操作。这些操作(包括 blockOperationWithBlock中的操作)能够在不同的线程中同时(并发)履行。只有当一切相关的操作现已完结履行时,才视为完结。
  2. 运用子类 BlockOperation,并调用办法AddExecutionBlock:的情况下,blockOperationWithBlock:办法中的操作 和addExecutionBlock:中的操作是在不同的线程中异步履行的。并且,这次履行成果中blockOperationWithBlock:办法中的操作也不是在当时线程(主线程)中履行的。从而印证了blockOperationWithBlock:中的操作也或许会在其他线程(非当时线程)中履行。
  3. 一般情况下,假如一个BlockOperation 目标封装了多个操作。BlockOperation 是否敞开新线程,取决于操作的个数。假如增加的操作的个数多,就会主动敞开新线程。当然敞开的线程数是由体系来决议的。

自界说 Operation

非并发 Operation

关于非并发的操作,在子类的 Operation 中,只需求复写 main() 就能够了。
在 main() 办法中,写一些使命履行的代码 等,另外在子类中或许还需求写一些初始化办法,以及一些拜访读取数据的办法等等。
至于 Operation 的几种状况,咱们是不需求关怀的,当 main() 办法履行完毕,即 Operation 使命结束。
Operation 类中还供给了 cancel() 的办法,所以在 Operation 履行的时分需求判别是否现已撤销了,由于撤销操作或许在开端之前就履行了,也或许在使命履行过程中,所以代码中需求参加 isCancelled 的判别。

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let op = MyOpertaion(data: "自界说Operation")
        op.start()
    }
}
class NonConcurrentOperation: Operation {
    var data: Any?
    init(data: Any) {
        super.init()
        self.data = data
    }
    override func main() {
        var isDone = false
        while !isCancelled && !isDone {
            // 在此办法中做一些相关业务操作等,并在完结后将isDone设置为true
            print("自界说Operation:\(Thread.current)")
            // ......
            isDone = true
        }
    }
}

运转成果:

自界说Operation:<NSThread: 0x6000030781c0>{number = 1, name = main}

并发 Operation

Operation 目标默许以同步办法履行,也便是说,在调用 start() 办法的线程中履行使命。可是,由于 OperationQueue 为非并发操作供给线程,所以大多数 Operation 仍然是异步运转的。可是,假如咱们手动运用 Operation,不必 OperationQueue,并且仍然期望它们异步运转,则有必要采纳恰当的操作来确保能够做到这一点。咱们能够经过将 Operation 目标界说为并发操作来完结这一点。至于并发的 Operation,稍微有些复杂了,由于这儿面的状况需求开发人员来管控。

创立一个并发的 Operation,则至少需求复写以下的办法和特点:

  • start()
  • isAsynchronous
  • isExecuting
  • isFinished
import Foundation
public class ConcurrentOperation: Operation {
  public var completedBlock: (() -> Void)?
  public override var isExecuting: Bool{
    return _executing
  }
  public override var isFinished: Bool{
    return _finished
  }
  public override var isAsynchronous: Bool{
    return true
  }
  // MARK: -
  // MARK: 利用 KVO 来通知 Operation 的 isExecuting(是否正在进行中),以及 isFinished(是否现已完结)
  //指定用于记录使命是否履行
  private var _executing:Bool = false{
    // kvo isExecuting
    willSet{
      willChangeValue(forKey: ModifyState.isExecuting.rawValue)
    }
    didSet{
      didChangeValue(forKey: ModifyState.isExecuting.rawValue)
    }
  }
  // 指定用于记录使命是否完结
  private var _finished:Bool = false{
    // kvo isFinished
    willSet{
      willChangeValue(forKey: ModifyState.isFinished.rawValue)
    }
    didSet{
      didChangeValue(forKey: ModifyState.isFinished.rawValue)
    }
  }
 
  // MARK: -
  /// 修正状况枚举(重写状况的字段标识)
  private enum ModifyState: String{
    case isExecuting = "isExecuting"
    case isFinished = "isFinished"
  }
    // MARK: 重写 start() 办法,开辟新的线程履行需求的耗时作业。
    // MARK: 注意! 不能调用父类的 start() 办法。
    // MARK: 由于 Operation 有 cancel() 办法来撤销操作,并且咱们并不知道在何时撤销,所以咱们需求在几个当地注意是否撤销了操作.
  public override func start() {
    // 检测撤销状况
    if isCancelled {
      done()
      return
    }
    // 修正状况 -> 履行
    _executing = true
    // 敞开使命->并行,完结回调测试
    startTask()
  }
  // 敞开使命(模仿耗时使命)
  private func startTask(){
    DispatchQueue.global().async { [weak self] in
      print("线程:",Thread.current)
      // 耗时
      sleep(2)
      for i in 0...2{
        print("\(i)")
      }
      // 检测状况
      if self?.isCancelled ?? false{
        self?.done()
        return
      }
      DispatchQueue.main.async { [weak self] in
        // 完结
        self?.completedBlock?()
        self?.done()
      }
    }
  }
  // 重写撤销
  public override func cancel() {
    // 加锁确保线程安全
    objc_sync_enter(self)
    done()
    objc_sync_exit(self)
  }
  // 自界说 cancel
  private func done() {
    super.cancel()
    print("done start",isCancelled)
    if(_executing) {
      _finished = true
      _executing = false
    }
    print("done end",isCancelled)
  }
}

创立行列

OperationQueue 一共有主行列、自界说行列两种行列。其间自界说行列同时包括了串行、并发功用。下边是主行列、自界说行列的基本创立办法和特点。

主行列

但凡增加到主行列中的操作,都会放到主线程中履行(注:不包括操作运用addExecutionBlock:增加的额定操作,额定操作或许在其他线程履行)。

主行列获取办法:

let mainQueue = OperationQueue.main

示例:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let mainQueue = OperationQueue.main
        let operation1 = BlockOperation {
            print("使命一:\(Thread.current)")
        }
        let opertaion2 = BlockOperation {
            print("使命二:\(Thread.current)")
        }
        let opertaion3 = BlockOperation {
            print("使命三:\(Thread.current)")
        }
        let opertaion4 = BlockOperation {
            print("使命四:\(Thread.current)")
        }
        mainQueue.addOperation(operation1)
        mainQueue.addOperation(opertaion2)
        mainQueue.addOperation(opertaion3)
        mainQueue.addOperation(opertaion4)
    }
}

运转成果:

使命一:<NSThread: 0x6000009bc280>{number = 1, name = main}
使命二:<NSThread: 0x6000009bc280>{number = 1, name = main}
使命三:<NSThread: 0x6000009bc280>{number = 1, name = main}
使命四:<NSThread: 0x6000009bc280>{number = 1, name = main}

自界说行列

增加到这种行列中的操作,就会主动放到子线程中履行。

自界说行列创立办法

let queue = OperationQueue()

示例:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let queue = OperationQueue()
        // 最大并发数
        queue.maxConcurrentOperationCount = 4
        let operation1 = BlockOperation {
            print("使命一 = \(Thread.current)")
        }
        let operation2 = BlockOperation {
            print("使命二 = \(Thread.current)")
        }
        let operation3 = BlockOperation {
            print("使命三 = \(Thread.current)")
        }
        let operation4 = BlockOperation {
            print("使命四 = \(Thread.current)")
        }
        queue.addOperation(operation1)
        queue.addOperation(operation2)
        queue.addOperation(operation3)
        queue.addOperation(operation4)
    }
}

运转成果:

使命三 = <NSThread: 0x600000bd6980>{number = 4, name = (null)}
使命二 = <NSThread: 0x600000ba1dc0>{number = 7, name = (null)}
使命一 = <NSThread: 0x600000bf6500>{number = 6, name = (null)}
使命四 = <NSThread: 0x600000bf7100>{number = 5, name = (null)}

OperationQueue 操控串行履行、并发履行

最大并发操作数:maxConcurrentOperationCount

  • maxConcurrentOperationCount默许情况下为 -1,表示不进行限制,可进行并发履行。
  • maxConcurrentOperationCount 等于 1 时,行列为串行行列。只能串行履行。
  • maxConcurrentOperationCount 大于 1 时,行列为并发行列。操作并发履行,当然这个值不应超过体系限制,即便自己设置一个很大的值,体系也会主动调整为 min {自己设定的值,体系设定的默许最大值}。

maxConcurrentOperationCount = 1 时

示例:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        print("start")
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1; // 串行行列
        queue.addOperation {
            sleep(2)
            print("0---\(Date())\(Thread.current)")
        }
        queue.addOperation {
            sleep(2)
            print("1---\(Date())\(Thread.current)")
        }
        let operation1 = BlockOperation.init {
            sleep(2)
            print("2---\(Date())\(Thread.current)")
        }
        operation1.addExecutionBlock {
            sleep(2)
            print("3---\(Date())\(Thread.current)")
        }
        queue.addOperation(operation1)
        print("end")
    }
}

运转成果:

start
end
0---2021-07-21 08:30:46 +0000<NSThread: 0x600003f23140>{number = 6, name = (null)}
1---2021-07-21 08:30:48 +0000<NSThread: 0x600003f2d540>{number = 5, name = (null)}
2---2021-07-21 08:30:50 +0000<NSThread: 0x600003f23140>{number = 6, name = (null)}
3---2021-07-21 08:30:50 +0000<NSThread: 0x600003f2d540>{number = 5, name = (null)}

maxConcurrentOperationCount > 1 时

示例:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        print("start")
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 4; // 串行行列
        queue.addOperation {
            sleep(2)
            print("0---\(Date())\(Thread.current)")
        }
        queue.addOperation {
            sleep(2)
            print("1---\(Date())\(Thread.current)")
        }
        let operation1 = BlockOperation.init {
            sleep(2)
            print("2---\(Date())\(Thread.current)")
        }
        operation1.addExecutionBlock {
            sleep(2)
            print("3---\(Date())\(Thread.current)")
        }
        queue.addOperation(operation1)
        print("end")
    }
}

运转成果:

start
end
0---2021-07-21 08:33:23 +0000<NSThread: 0x600001aa89c0>{number = 2, name = (null)}
1---2021-07-21 08:33:23 +0000<NSThread: 0x600001aace00>{number = 6, name = (null)}
2---2021-07-21 08:33:23 +0000<NSThread: 0x600001aa07c0>{number = 4, name = (null)}
3---2021-07-21 08:33:23 +0000<NSThread: 0x600001aa85c0>{number = 3, name = (null)}

Operation 操作依靠

Operation、OperationQueue 最吸引人的当地是它能增加操作之间的依靠联系。经过操作依靠,咱们能够很便利的操控操作之间的履行先后次序。Operation 供给了 3 个接口供咱们管理和检查依靠。

增加依靠

func addDependency(_ op: Operation);增加依靠,使当时操作依靠于操作 op 的完结。

示例:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        testDependency()
    }
    //测试op依靠联系
    //A,B - C
    //C,D - E
    func testDependency(){
        let opA = BlockOperation()
        let opB = BlockOperation()
        let opC = BlockOperation()
        let opD = BlockOperation()
        let opE = BlockOperation()
        ///创立使命
        opA.addExecutionBlock {
            for i in 0...10{
                if i == 10{
                    print("A--\(i)")
                }
            }
        }
        opB.addExecutionBlock {
            for i in 0...10{
                if i == 10{
                    print("B--\(i)")
                }
            }
        }
        opC.addExecutionBlock {
            for i in 0...10{
                if i == 10{
                    print("C--\(i)")
                }
            }
        }
        opD.addExecutionBlock {
            for i in 0...10{
                if i == 10{
                    print("D--\(i)")
                }
            }
        }
        opE.addExecutionBlock {
            for i in 0...10{
                if i == 10{
                    print("E--\(i)")
                }
            }
        }
        ///增加依靠
        opC.addDependency(opA)
        opC.addDependency(opB)
        opE.addDependency(opC)
        opE.addDependency(opD)
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 6
        queue.addOperations([opA,opB,opC,opD,opE], waitUntilFinished: false)
        print("end")
    }
}

移除依靠

func removeDependency(_ op: Operation);移除依靠,撤销当时操刁难操作 op 的依靠。

获取依靠

var dependencies: [Operation] { get };在当时操作开端履行之前完结履行的一切操作目标数组。

当心死锁:假如Operation之间相互依靠,比方行列 A 中的 Operation1 依靠Operation2, 而 Operation2 依靠 Operation3Operation3 依靠 Operation1, 这就会堕入相互等候的死锁。

Operation 优先级

Operation 供给了 queuePriority(优先级)特点,queuePriority 特点适用于同一操作行列中的操作,不适用于不同操作行列中的操作。默许情况下,一切新创立的操作目标优先级都是 normal。可是咱们能够经过 setQueuePriority: 办法来改动当时操作在同一行列中的履行优先级。

优先级的取值:

public enum QueuePriority : Int {
  case veryLow = -8
  case low = -4
  case normal = 0
  case high = 4
  case veryHigh = 8
}

上边咱们说过:关于增加到行列中的操作,首要进入准备安排妥当的状况(安排妥当状况取决于操作之间的依靠联系),然后进入安排妥当状况的操作的开端履行次序(非结束履行次序)由操作之间相对的优先级决议(优先级是操作目标自身的特点)。

那么,什么样的操作才是进入安排妥当状况的操作呢?
当一个操作的一切依靠都现已完结时,操作目标通常会进入准备安排妥当状况,等候履行。

举个比如,现在有 4 个优先级都是 normal(默许等级)的操作:op1,op2,op3,op4。其间 op3 依靠于 op2,op2 依靠于 op1,即 op3 -> op2 -> op1。现在将这 4 个操作增加到行列中并发履行。

  • 由于 op1 和 op4 都没有需求依靠的操作,所以在 op1,op4 履行之前,便是处于准备安排妥当状况的操作。

  • 而 op3 和 op2 都有依靠的操作(op3 依靠于 op2,op2 依靠于 op1),所以 op3 和 op2 都不是准备安排妥当状况下的操作。理解了进入安排妥当状况的操作,那么咱们就理解了queuePriority 特点的作用目标。

  • queuePriority 特点决议了进入准备安排妥当状况下的操作之间的开端履行次序。并且,优先级不能替代依靠联系。

  • 假如一个行列中既包括高优先级操作,又包括低优先级操作,并且两个操作都现已准备安排妥当,那么行列先履行高优先级操作。比方上例中,假如 op1 和 op4 是不同优先级的操作,那么就会先履行优先级高的操作。

  • 假如,一个行列中既包括了准备安排妥当状况的操作,又包括了未准备安排妥当的操作,未准备安排妥当的操作优先级比准备安排妥当的操作优先级高。那么,尽管准备安排妥当的操作优先级低,也会优先履行。优先级不能替代依靠联系。假如要操控操作间的发动次序,则有必要运用依靠联系。

示例:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        testQueuePriority()
    }
    func testQueuePriority(){
        let opA = BlockOperation()
        let opB = BlockOperation()
        let opC = BlockOperation()
        opA.addExecutionBlock {
            print("使命一:\(Thread.current)")
        }
        opB.addExecutionBlock {
            print("使命二:\(Thread.current)")
        }
        opC.addExecutionBlock {
            print("使命三:\(Thread.current)")
        }
        opA.queuePriority = .low
        opB.queuePriority = .high
        opC.queuePriority = .normal
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 3
        queue.addOperations([opA, opB, opC], waitUntilFinished: false)
    }
}

Operation、OperationQueue 线程间的通讯

在 iOS 开发过程中,咱们一般在主线程里面进行 UI 改写,例如:点击、滚动、拖拽等事情。咱们通常把一些耗时的操作放在其他线程,比方说图片下载、文件上传等耗时操作。而当咱们有时分在其他线程完结了耗时操作时,需求回到主线程,那么就用到了线程之间的通讯。

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        communication()
    }
    ///线程间通讯
    func communication() {
        OperationQueue().addOperation {
            sleep(2)
            print("1---\(Date())\(Thread.current)")
            OperationQueue.main.addOperation({
                sleep(2)
                print("2---\(Date())\(Thread.current)")
            })
        }
    }
}

能够看到:经过线程间的通讯,先在其他线程中履行操作,等操作履行完了之后再回到主线程履行主线程的相应操作。

Operation、OperationQueue 线程同步和线程安全

线程同步

  • 线程同步:
    可理解为线程 A 和 线程 B 一块配合,A 履行到必定程度时要依靠线程 B 的某个成果,于是停下来,暗示 B 运转;B 依言履行,再将成果给 A;A 再继续操作。

  • 若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时履行写操作(更改动量),一般都需求考虑线程同步,否则的话就或许影响线程安全。

线程安全

  • 线程安全:
    假如你的代码所在的进程中有多个线程在同时运转,而这些线程或许会同时运转这段代码。假如每次运转成果和单线程运转的成果是相同的,并且其他的变量的值也和预期的是相同的,便是线程安全的。

  • 线程安全处理方案:
    能够给线程加锁,在一个线程履行该操作的时分,不允许其他线程进行操作。iOS 完结线程加锁有很多种办法。@synchronized、 NSLock、RecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock、atomic(property) set/get 等等各种办法。这儿咱们运用 NSLock 目标来处理线程同步问题。NSLock 目标能够经过进入锁时调用 lock 办法,解锁时调用 unlock 办法来确保线程安全。

考虑线程安全的代码:

class ViewController: UIViewController {
    var ticketSurplusCount = 50
    override func viewDidLoad() {
        super.viewDidLoad()
        ///1.1 创立代表北京火车票售卖窗口
        let operationForBeiJing = OperationQueue()
        operationForBeiJing.maxConcurrentOperationCount = 1;
        ///1.2 创立卖票操作 op1
        let op1 = BlockOperation{ 
            self.saleTicketSafe()
        }
        ///1.3 增加操作
        operationForBeiJing.addOperation(op1)
        ///2.1创立代表上海火车票售卖窗口
        let operationForShangHai = OperationQueue()
        operationForShangHai.maxConcurrentOperationCount = 1;
        ///2.2创立卖票操作 op2
        let op2 = BlockOperation{
            self.saleTicketSafe()
        }
        ///2.3 增加操作
        operationForShangHai.addOperation(op2)
    }
    private func saleTicketSafe(){
        while true {
            objc_sync_enter(self)
            if self.ticketSurplusCount > 0 {
                self.ticketSurplusCount-=1;
                print("剩余票数:\(self.ticketSurplusCount) 窗口:\(Thread.current)")
                sleep(2)
            }
            objc_sync_exit(self)
            if self.ticketSurplusCount <= 0 {
                print("一切火车票均已售完")
                break
            }
        }
    }
}