文章主要记录了iOS中多线程的基础概念及使用方法,在此作一个记录。一是加深印象,之后本身使用时也能够方便查找及复习,二是在本身的学习过程当中,总有大牛的文章做为引导,但愿本身也能给须要这方面知识的人一些帮助。git
关于这篇文章的Demo能够去个人github中MultiThreadDemo查看源码,若有不当之处,但愿你们指出。程序员
GCD方面的知识点,后续会继续更新。。。github
优势api
缺点数组
实际上,使用多线程,因为会开线程,必然就会消耗性能,可是却能够提升用户体验。因此,综合考虑,在保证良好的用户体验的前提下,能够适当地开线程。bash
在iOS中每一个进程启动后都会创建一个主线程(UI线程)。因为在iOS中除了主线程,其余子线程是独立于Cocoa Touch的,因此只有主线程能够更新UI界面。iOS中多线程使用并不复杂,关键是如何控制好各个线程的执行顺序、处理好资源竞争问题。多线程
接下来就介绍一下iOS常见的几种多线程实现方式。并发
这里介绍Thread的三种建立方式。下方三中建立方式中的Target类为:app
class Receiver: NSObject {
@objc func runThread() {
print(Thread.current)
}
}
复制代码
// 1.建立线程
let thread_one = Thread(target: Receiver(), selector: #selector(Receiver.runThread), object: nil)
let thread_two = Thread {
// TODO
}
// 2.启动线程
thread_one.start()
thread_two.start()
复制代码
// 建立线程后自动启动线程
Thread.detachNewThread {
// TODO
}
Thread.detachNewThreadSelector(#selector(Receiver.runThread), toTarget: Receiver(), with: nil)
复制代码
let obj = Receiver()
// 隐式建立并启动线程
obj.performSelector(inBackground: #selector(obj.runThread), with: nil)
复制代码
// 去主线程执行指定方法
performSelector(onMainThread: Selector, with: Any?, waitUntilDone: Bool, modes: [String]?)
// 去指定线程执行方法
perform(aSelector: Selector, on: Thread, with: Any?, waitUntilDone: Bool, modes: [String]?)
复制代码
设置线程优先级时,接收一个Double类型。异步
数值范围为:0.0 ~ 1.0。
对于新建立的thread来讲,Priority的值通常是 0.5。可是,由于优先级是由系统内核决定的,并不能保证这个值会是什么。
var threadPriority: Double { get set }
复制代码
与线程状态及生命周期相关的函数:
// - 启动线程的方法,进入就绪状态等待CPU调用
func start()
// - 阻塞(暂停)线程方法,进入阻塞状态
class func sleep(until date: Date)
class func sleep(forTimeInterval ti: TimeInterval)
// - 取消线程的操做,在线程执行完当前操做后,不会再继续执行任务
func cancel()
// - 强制中止线程,进入死亡状态
class func exit()
复制代码
cancel():方法并非当即取消当前线程,而是更改线程的状态,以指示它应该退出。
exit():应该避免调用此方法,由于它不会让线程有机会清理它在执行期间分配的任何资源。
系统还定义了几个NSNotification。若你对当前线程状态的改变感兴趣,能够订阅这几个通知:
// 当除了主线程外的最后一个线程退出时
static let NSDidBecomeSingleThreaded: NSNotification.Name
// 当线程接收到exit()消息时
static let NSThreadWillExit: NSNotification.Name
// 当建立第一个除主线程外的子线程时发布,然后再建立子线程时不会再发出通知。
// 通知的观察者的通知方法在主线程调用
NSWillBecomeMultiThreaded: NSNotification.Name
复制代码
// 获取主线程
Thread.main
// 获取当前线程
Thread.current
// 获取当前线程状态
Thread.current.isCancelled
Thread.current.isFinished
Thread.current.isFinished
复制代码
Operation是一个抽象类,能够用来封装一个任务,其中包含代码逻辑和数据。由于Operation是抽象类,因此编写代码时不能直接使用,要使用它的子类,系统默认提供的有NSInvocationOperation(Swift中不可用)和BlockOperation。
OperationQueue(操做队列)是用来控制一系列操做对象执行的。操做对象被添加进队列后,一直存在到操做被取消或者执行完成。队列里的操做对象执行的顺序由操做的优先级和操做之间的依赖决定。一个应用里能够建立多个队列进行操做处理。
由Operation 和 OperationQueue的介绍能够获得使用步骤:
以后呢,系统就会自动将OperationQueue的Operation取出来,在新线程中执行操做。
默认是不会开启线程的,只会在当前的线程中执行操做,能够经过Operation和OperationQueue实现多线程。
// 1.建立 NSInvocationOperation 对象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
// 2.调用 start 方法开始执行操做
// 不会开启线程
[op start];
复制代码
BlockOperation 是否开启新线程,取决于操做的个数。若是添加的操做的个数多,就会自动开启新线程。固然开启的线程数是由系统来决定的。
// 1. 建立BlockOperation对象,并封装操做
let op = BlockOperation.init {
print("init + \(Thread.current)")
}
// 2. 调用 start 方法开始执行操做
op.start()
复制代码
默认状况下,Operation的子类是同步执行的,若是要建立一个可以并发的子类,咱们可能须要重写一些方法。
复制代码
OperationQueue 一共有两种队列:主队列、自定义队列。其中自定义队列同时包含了串行、并发功能。下边是主队列、自定义队列的基本建立方法和特色。
// 主队列获取方法
let mainQueue = OperationQueue.main
// 自定义队列建立方法
let queue = OperationQueue()
复制代码
Operation 须要配合 OperationQueue来实现多线程。咱们须要将建立好的操做加入到队列中去。有两种方法:
将建立好的Operation或其子类的实例对象直接添加。
直接经过block的方式添加一个操做至队列中。
OperationQueue 建立的自定义队列同时具备串行、并发功能。它的串行功能是经过属性 最大并发操做数—maxConcurrentOperationCount用来控制一个特定队列中能够有多少个操做同时参与并发执行。
注意:这里 maxConcurrentOperationCount控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操做数。并且一个操做也并不是只能在一个线程中运行。
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.addOperation {
sleep(1)
print("1---\(Thread.current)----\(Date.timeIntervalSinceReferenceDate)")
}
queue.addOperation {
sleep(1)
print("2---\(Thread.current)----\(Date.timeIntervalSinceReferenceDate)")
}
queue.addOperation {
sleep(1)
print("3---\(Thread.current)----\(Date.timeIntervalSinceReferenceDate)")
}
queue.addOperation {
sleep(1)
print("4---\(Thread.current)----\(Date.timeIntervalSinceReferenceDate)")
}
-----最大并发操做数为1,输出结果:------
1---<NSThread: 0x600001ddc200>{number = 5, name = (null)}----576945144.766482
2---<NSThread: 0x600001dd0280>{number = 6, name = (null)}----576945145.775298
3---<NSThread: 0x600001dfbd00>{number = 4, name = (null)}----576945146.775842
4---<NSThread: 0x600001dd0280>{number = 6, name = (null)}----576945147.779273
-----最大并发操做数为3,输出结果:------
2---<NSThread: 0x6000018dc0c0>{number = 5, name = (null)}----576945253.401897
1---<NSThread: 0x6000018c5d00>{number = 7, name = (null)}----576945253.401891
3---<NSThread: 0x6000018ca540>{number = 6, name = (null)}----576945253.401913
4---<NSThread: 0x6000018dc100>{number = 8, name = (null)}----576945254.403032
复制代码
上方输出的结果中,分析线程及输出时间能够看出:从当最大并发操做数为1时,操做是按顺序串行执行的。当最大操做并发数为3时,有3个操做是并发执行的,延迟1s后执行另外一个。而开启线程数量是由系统决定的,不须要咱们来管理。
Operation 提供了3个接口供咱们管理和查看依赖。
// 添加依赖,使当前操做依赖于操做 op 的完成。
func addDependency(_ op: Operation)
// 移除依赖,取消当前操做对操做 op 的依赖。
func removeDependency(_ op: Operation)
// 必须在当前对象开始执行以前完成执行的操做对象数组。
var dependencies: [Operation] { get }
复制代码
经过添加操做依赖,不管运行几回,其结果都是 op2 先执行,op1 后执行。
let queue = OperationQueue()
let op1 = BlockOperation {
print("op1")
}
let op2 = BlockOperation {
print("op2")
}
op1.addDependency(op2)
queue.addOperation(op1)
queue.addOperation(op2)
----输出结果:----
op2
op1
复制代码
OperationQueue 提供了queuePriority(优先级)属性,queuePriority属性适用于同一操做队列中的操做,不适用于不一样操做队列中的操做。默认状况下,全部新建立的操做对象优先级都是normal。可是咱们能够经过赋值来改变当前操做在同一队列中的执行优先级。
// 优先级的取值
public enum QueuePriority : Int {
case veryLow
case low
case normal // default value
case high
case veryHigh
}
复制代码
对于添加到队列中的操做,首先进入准备就绪的状态(就绪状态取决于操做之间的依赖关系),而后进入就绪状态的操做的开始执行顺序(非结束执行顺序)由操做之间相对的优先级决定(优先级是操做对象自身的属性)。
理解了进入就绪状态的操做,那么咱们就理解了queuePriority 属性的做用对象。
let queue = OperationQueue()
let op = BlockOperation {
print("异步操做 -- \(Thread.current)")
// 回到主线程
OperationQueue.main.addOperation({
print("回到主线程了 -- \(Thread.current)")
})
}
queue.addOperation(op)
-----输出结果:-----
异步操做 -- <NSThread: 0x60000102f540>{number = 3, name = (null)}
回到主线程了 -- <NSThread: 0x60000100d680>{number = 1, name = main}
复制代码
1. 取消操做的方法
* func cancel() 可取消操做,实质是标记 isCancelled 状态。
2. 判断操做状态的方法
* isFinished 判断操做是否已经结束。
* isCancelled 判断操做是否已经标记为取消。
* isExecuting 判断操做是否正在在运行。
* isAsynchronous 判断操做是否异步执行其任务。
* isReady 判断操做是否处于准备就绪状态,这个值和操做的依赖关系相关。
3. 操做同步
* func waitUntilFinished() 阻塞当前线程,直到该操做结束。可用于线程执行顺序的同步。
* completionBlock: (() -> Void)? 会在当前操做执行完毕时执行 completionBlock。
复制代码
1. 取消/暂停/恢复操做
* func cancelAllOperations() 能够取消队列的全部操做。
* isSuspended 判断及设置队列是否处于暂停状态。true为暂停状态,false为恢复状态。
2. 操做同步
* func waitUntilAllOperationsAreFinished() 阻塞当前线程,直到队列中的操做所有执行完毕。
3. 添加/获取操做
* func addOperations(_ ops: [Operation], waitUntilFinished wait: Bool) 向队列中添加操做数组,wait 标志是否阻塞当前线程直到全部操做结束
* operations 当前在队列中的操做数组(某个操做执行结束后会自动从这个数组清除)。
* operationCount 当前队列中的操做数。
4. 获取队列
* current 获取当前队列,若是当前线程不是在 OperationQueue 上运行则返回 nil。
* main 获取主队列。
复制代码
注意:
- 这里的暂停和取消(包括操做的取消和队列的取消)并不表明能够将当前的操做当即取消,而是当当前的操做执行完毕以后再也不执行新的操做。
- 暂停和取消的区别就在于:暂停操做以后还能够恢复操做,继续向下执行;而取消操做以后,全部的操做就清空了,没法再接着执行剩下的操做。