0. 前言
NSThread 是 iOS 多线程当中最基础最轻量的多线程技术,但是需要自行管理线程的生命周期和同步问题,所以使用起来其实比较麻烦。
在 Swift 3 中,NSThread 已经被重命名为 Thread,很多的方法名也进行了修改。
下面就写一下自己学到的东西,当作学习笔记,以后可以翻看。如果有错误的地方望指正。
1. 创建线程和获取线程
- 直接创建线程是最基本的创建方式,可以获取到线程线程对象,适合需要对线程对象进行操作的情况。需要手动启动。
//直接创建
let thread = Thread.init(target: self, selector: Selector("run"), object: nil)
thread.name = "firstThread"
thread.start()
- 分离子线程,不能获取到线程对象,创建之后自启动。
//分离子线程
Thread.detachNewThreadSelector(Selector("run"), toTarget: self, with: nil)
- 开启一条后台线程,同样不能获取到线程对象,创建后自启动。
//后台线程
self.performSelector(inBackground: Selector("run"), with: nil)
- 自定义线程类继承自Thread,对内部的方法进行重写封装,满足自定义需求。
//自定义线程
let customThread = CustomThread()
customThread.start()
- 获取线程
//获取主线程
let mainThread = Thread.main
//获取当前线程
let currentThread = Thread.current
//判断当前线程是否为主线程 返回类型为Bool
let currentIsMain = Thread.isMainThread
2. 线程状态
//退出线程
Thread.exit()
//线程休眠给定时间
Thread.sleep(forTimeInterval: 1.0)
//线程休眠至特定时间
Thread.sleep(until: Date(timeIntervalSinceNow: 1.0))
3. 线程同步与加锁
关于同步和加锁,是多线程逃不掉的话题。Thread 中的线程同步一般用 NSCondition 来加锁实现。NSCondition(这个没有改名字…)主要使用的是 lock()、wait() 和 unlock()。
苹果的 API 文档是这么描述 NSCondition 的:
The NSCondition class implements a condition variable whose semantics follow those used for POSIX-style conditions.
A condition object acts as both a lock and a checkpoint in a given thread.
意思就是 NSCondition 所描述的条件对象在线程当中同时扮演锁(lock)和检查点(checkpoint)的角色。
The lock protects your code while it tests the condition and performs the task triggered by the condition.
The checkpoint behavior requires that the condition be true before the thread proceeds with its task.
While the condition is not true, the thread blocks. It remains blocked until another thread signals the condition object.
Lock 保护着你…… 的代码,以保证它们能按照逻辑来运行和满足条件时的触发执行。
线程要干活的时候需要 checkpoint 的条件为真,不然就阻塞罢工,直到其它线程给它发信号唤醒它。
下面是一个 🌰
class ViewController : UIViewController {
//声明两个线程
var thread_1: Thread?
var thread_2: Thread?
//创建两个 NSCondition
let condition_1 = NSCondition()
let condition_2 = NSCondition()
override func viewDidLoad() {
super.viewDidLoad()
//两个线程的创建和 thread_1 的启动
thread_1 = Thread(target: self, selector: #selector(self.forThreadOne) , object: nil)
thread_2 = Thread(target: self, selector: #selector(self.forThreadTwo), object: nil)
thread_1?.start()
}
//thread_1 需要执行的方法
@objc func forThreadOne() {
for i in 1...7 {
print("Thread_1 is running for the #\(i) time(s).")
if i % 2 == 0 {
//判断 thread_2 是否在运行中,没运行就启动,如果在运行就发信号唤醒 thread_2
if thread_2!.isExecuting {
condition_2.signal()
} else {
thread_2?.start()
}
//锁住 thread_1
condition_1.lock()
condition_1.wait()
condition_1.unlock()
}
}
print("Thread_1 says it is ended.")
}
//thread_2 需要执行的方法
@objc func forThreadTwo() {
for i in 1...7 {
print("Thread_2 is running for the #\(i) time(s).")
if i % 2 == 0 {
//发信号唤醒 thread_1
condition_1.signal()
//锁住 thread_2
condition_2.lock()
condition_2.wait()
condition_2.unlock()
}
}
print("Thread_2 says it is over.")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Swift 还可以使用 objc_sync_enter(obj: Any!) 和 objc_sync_exit(obj: Any!) 来加互斥锁,二者必须成对使用,把需要加锁的代码放在它们两个中间。
比如举一个买 7 本书的 🌰
var amountOfBooks = 7
...
func run() {
while(true) {
objc_sync_enter(self)
let current = amountOfBooks
if current > 0 {
amountOfBooks = current - 1
print(...)
} else {
print(...)
break
}
objc_sync_exit(self)
}
}
题外话
Swift 需要成对使用的方法很多,多到可以写一个总结…… 先挖个坑
4. 线程通信
主要涉及到的方法有三个,当前线程的、主线程的、指定线程的。
//当前线程
self.perform(#selector(self.run), with: nil)
//主线程
self.performSelector(onMainThread: #selector(self.run), with: nil, waitUntilDone: true)
//指定线程
self.perform(#selector(self.run), on: anotherThread, with: nil, waitUntilDone: true)
举一个非常简单的小 🌰,在某个 ViewController的viewDidLoad 里,开启了一个子线程,运行 createNumber() 方法,声明 number 并赋值 7。
完了以后,使用 performSelector(onMainThread:...) 切换到主线程,并且跑起 printNumber() 方法,把 number 作为参数传入。
完成简单的线程间通信传值。
class ViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Thread.detachNewThreadSelector(#selector(self.createNumber), toTarget: self, with: nil)
}
@objc func createNumber() {
let number = 7
print("Current Thread: \(Thread.current)")
self.performSelector(onMainThread: #selector(self.printNumber(number:)), with: number, waitUntilDone: true)
}
@objc func printNumber(number: Int) {
print("The number is: \(number).")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
题外话 x 2
#selector(@objc method) 是非常不 Swift 3 的写法,要求传入的是一个 @objc 的方法。
使用 Selector("methodWithoutParameter") 或者 Selector("methodWithParameter:") 的话就不用在方法前加 @objc 标签,但是会爆黄……