网络请求实例切换
背景介绍
这一章来模拟网络请求:在APP中请求网络数据(任务A: 耗时10s),获取数据后进行一定的处理(任务B: 耗时5s),最后刷新UI。
假如A和B都是同步任务,放主队列会死锁,而放其他任何队列,界面都会卡死15s,如果不信,把下面代码里的两种线程休眠方法(二选一,其实不止这两种),放在APP UIViewController里试试:
override func viewDidAppear(_ animated: Bool) {
//1. 全局队列执行同步任务
DispatchQueue.global().sync {
sleep(15)//当前线程休眠15秒
}
//2. 主队列执行异步任务
DispatchQueue.main.async {
sleep(15)//当前线程休眠15秒
}
}
不出所料,两种方法,均让界面卡死15s。回想一下上文说过的:所有的同步任务最终都要安排到主线程运行,主线程运行长耗时任务都会导致界面严重卡顿,所以:
能异步执行的长耗时任务,千万不要同步执行。 长耗时同步任务欠下的债,都由界面来偿还。
假如A和B都是异步任务,即使这样,你也不能都放在主队列中处理,这样也会导致APP界面卡住15s,因为上面说到了:主线程运行长耗时任务都会导致界面严重卡顿。
所有的长耗时任务,千万不要放在主队列中执行。 主队列长耗时异步任务欠下的债,也都由界面来偿还。
说了那么多,你现在应该能够深切地理解各种队列的运行原理了。
现在讲讲使用GCD多线程处理网络请求的正确做法:A、B都定义成异步任务,在并行队列中嵌套异步任务,最后切换到主队列去刷新UI,这样做界面可以保证最流畅。
//创建并行队列,尽量用自定义队列,免得自己的代码质量不过关,影响全局队列
let queue = DispatchQueue(label: “com.apple.request”, attributes: .concurrent)
可以看到队列和线程均进行了预期的切换,GCD队列切换像俄罗斯套娃一样,一层一层的嵌套就行,等嵌套出问题了,去第6章死锁分析寻找原因进行修改即可。
//异步执行
queue.async {
print("开始请求数据 \(Date()) thread: \(Thread.current)")
sleep(10)//模拟网络请求
print("数据请求完成 \(Date()) thread: \(Thread.current)")
//异步执行
queue.async {
print("开始处理数据 \(Date()) thread: \(Thread.current)")
sleep(5)//模拟数据处理
print("数据处理完成 \(Date()) thread: \(Thread.current)")
//切换到主队列,刷新UI
DispatchQueue.main.async {
print("UI刷新成功 \(Date()) thread: \(Thread.current)")
}
}
}
//运行结果
开始请求数据 2020-08-06 06:40:57 +0000 thread: <NSThread: 0x7ff917d8c0c0>{number = 4, name = (null)}
数据请求完成 2020-08-06 06:41:07 +0000 thread: <NSThread: 0x7ff917d8c0c0>{number = 4, name = (null)}
开始处理数据 2020-08-06 06:41:07 +0000 thread: <NSThread: 0x7ff8f7d0c190>{number = 3, name = (null)}
数据处理完成 2020-08-06 06:41:12 +0000 thread: <NSThread: 0x7ff8f7d0c190>{number = 3, name = (null)}
UI刷新成功 2020-08-06 06:41:12 +0000 thread: <NSThread: 0x7ff917c0e7e0>{number = 1, name = main}