1. 队列
- 多线程很大程度上都与iOS的队列有关
- 函数或方法(通常是闭包)会被排成一个队列来执行,而这些方法会被放进队列中,并且会在一个与之关联的线程中被执行
- 队列可以是串行的“serial”(一次只执行一个闭包),也可以是并行的“concurrent”(多个线程同时在为他服务),多核cpu可以并列执行任务,单核cpu也能通过细分时间来实现并行。
2. 主队列
- 主队列是一个很特殊的串行队列,所有和UI相关的活动,都发生在主队列中,而且也只能发生在主队列中。
- 相反的,一些非UI操作,且十分耗时的活动,不应该出现在主线程中!原因:我们希望用户的UI操作是高度可响应的。举个例子,加入用户点击了下载图片按钮,在图片下载完成之前,用户的UI界面不应该被卡住,而是可操作,用户可以决定是否要返回上一级,或者退出当前菜单,甚至取消下载,而不是一个等待的菊花不停旋转。
- 注意:当我们在其他非主队列进行操作后,如果想对UI进行操作,必须先返回主队列,再进行更新UI
3. 全局队列
- 对于一些不在主队列执行的任务,通常会用共享的,全局的,并发的队列
4. iOS中如何获得队列?
4.1 获得一个队列
//1. 获得一个主队列
let mainQueue = DispatchQueue.main
//2. 获得全局,并发,在后台运行的队列
let backgroudQueue = DispatchQueue.global(qos : DispatchQos)
DispatchQueue.userInteractive // 高优先级,适合又短又快的任务,很少用,可以在主线程上做
DispatchQueue.userInitiated // 高优先级,适合有一点耗时的任务,比较常用
DispatchQueue.backgroud // 低优先级,不直接被用户创建,所以可以做很耗时的任务
DispatchQueue.utility // 低优先级,长时间的后台处理
4.2 在队列中添加代码
- 通常用闭包在多线程中添加代码,通常有两种添加代码的方式
- 异步执行:保持当前队列运行,并在另一条队列中添加任务
queue.async { ... }
- 同步执行:阻止当前队列运行,等待加入队列中的任务完成后再继续执行
queue.sync { ... }
- 通常情况下,async使用的更多
4.3 创建你自己的队列
- 在极少数情况下,我们需要一些既不是主队列,也不是全局队列的队列
//创建一个自己的串行队列
let serialQueue = DispatchQueue(label: "MySerialQ")
//创建一个自己的并行队列
let concurrentQueue = DispatchQueue(label: "MyConcurrentQ", attributes: .concurrent)
4.4 OperationQueue 和 Operation
- 在需要做一些复杂的多线程任务时,比如任务C依赖任务A和B,而任务D又依赖任务A和C,Operation API 会起到很大的作用,它能提供不同线程的依赖关系。
4.5 iOS API中的多线程
- 下面这个iOS的API会让我们跳出主队列,进行数据的获取
let session = URLSession(configuration: .default)
if let url = URL(string: "http://www.baidu.com") {
let task = session.dataTask(with:url) { (data: Data? , response , error ) in
// **在这里能用下载的data来更新我的UI界面的吗?**
xxx...
}
task.resume()
}
- 在上面dataTask的尾随闭包中,可以用下载的data来更新我的UI界面吗?
- 答案是:不行。因为在这个闭包中,代码并非在主队列中执行,UI不能再非主队列之外的地方执行。
- 解决方案: 只需要返回主队列,再进行更新UI就可以了
let session = URLSession(configuration: .default)
if let url = URL(string: "http://www.baidu.com") {
let task = session.dataTask(with:url) { (data: Data? , response , error ) in
Dispatch.main.async {
// 这里可以更新UI啦!
}
}
task.resume()
}
4.6 异步执行顺序
异步执行顺序.png- 第一步:a。将String转换成url。
- 第二步:b。这一行代码除了创建了一个dataTask并把它传给task之外什么也没做,b行被执行后会立即返回,等待数据下载完成后才执行闭包里的代码。
- 第三步:g。g做的是在其他队列中获取数据。g行在b行后会被立即执行,也同时会立即返回。
- 第四步:h。打印“已经发起了url请求”。这时已经跳出了第一个大括号。a→b→g→h被执行,它们是一个接一个地被执行,中间没有任何停顿,而c,d,e,f行会在其他另一个队列中被执行。
- 第五步:c。在几秒钟,或者是几分钟,甚至时几小时后,数据终于返回,c行被执行,在这里可以进行处理数据
- 第六步:d。在这里发起返回主队列的异步请求。由于主队列可能正在进行其他任务,所以不一定会立即响应,e不会被立即执行。
- 第七步:f。打印“做一些处理data数据的工作,但此时UI还没有更新”。
- 第八步:e。等待主队列没有任务在执行了,终于轮到e执行,可以更新UI了。
- 总结:a→b→g→h→c→d→f→e。但是这不是唯一的可能性。因为当d执行后,主队列可能正好是空闲的,所以会立即执行e,此时e和f是同时进行的,所以也可能发生a→b→g→h→c→d→e→f。
请求图片的实例代码
if let url = imageURL { // 获取一个url地址
DispatchQueue.global(qos: .userInitiated).async { //在全局并发线程中,发起一个异步请求
let urlContents = try ? Data(contentsOf: url) //用url地址去发起请求,将返回结果赋值给urlContents
if let imageData = urlContents { // 如果返回的内容非空...,赋值给imageData
self.image = UIImage(data: imageData) //给self.image赋值
}
}
}
- 注意:当用户在还没有下载完成之前离开了界面,即用户不再需要这个数据,数据下载便没有了意义,这个数据和这个ViewController都不应该继续存在于堆中。但是由于上面代码的闭包中self.image一直指向ViewController,导致ViewController一直被保存在堆中,这就导致了资源的浪费。
- 解决方法:通过【weak self】,弱引用self,当用户退出界面,没有指针指向self,此时【weak self】闭包中的self也被设为nil
if let url = imageURL {
DispatchQueue.global(qos: .userInitiated).async { *** [ weak self ] in *** //注意这里
let urlContents = try ? Data(contentsOf: url)
if let imageData = urlContents {
self?.image = UIImage(data: imageData) //self.image 变成了self?.image, 当self不存在时,不执行
}
}
}
- 当然上面的代码仍然有问题,因为
self?.image = UIImage(data: imageData)
涉及到更新UI事件,而上面代码这句话并不是在主队列中执行的,所以需要回到主队列,代码如下 - 问题2,加入下载数据需要花费5分钟,五分钟后下载的数据并不是我当前需要的数据了,那也不需要更新UI了,所以需要在设置UI之前加一次判断
if let url = imageURL {
DispatchQueue.global(qos: .userInitiated).async { *** [ weak self ] in *** //注意这里
let urlContents = try ? Data(contentsOf: url)
DispatchQueue.main.async { //回到主队列
if let imageData = urlContents ,url == self?.imageURL { //加一层判断,保证返回的数据是我所需要的
self?.image = UIImage(data: imageData) //self.image 变成了self?.image, 当self不存在时,不执行
}
}
}
}
网友评论