最近项目中有个需求 ---- 选择多个内容然后进入打印预览页面(公司自己的打印机需要预览然后打印)
碰到的问题是, 选择列表中的多个数据, 我只能拿到数据的 id
, 因为列表就是简单的列表没有那么多详情数据在里面, 而后详情数据中还有图片需要下载, 下载完图片才可以进入打印预览页面, 相当于把数据都拿到后再给用户展示打印预览的样子
等同于多个详情的接口请求到结果后下载详情内的图片然后再去做一件事情
此时考虑用到选择内容较多的时候用户反正也是要 loading 等待, 所以请求接口就是异步但是按照同步去处理, 这样就不需要根据选好的 id 排序一遍, 如果是异步的话需要重新排序
但是图片还是要异步下载的, 异步下载的时候根据图片数量判断是否下载成功, 成功后才算数据完整, 这里又需要阻塞等待了
最终我的构思是这样的:
- for 循环一下选择的
id
构成的数组, 并创建一个变量var cango = false
- 使用网络请求框架(比如 AF)进行详情请求
- 请求到结果后, 将图片整理在一起(合并成一个数组), 这里就不需要特意找下载框架了,
let data = try? Data(contentsOf: url) let image = UIImage(data: data)
, 系统的这个请求本身是同步的, 自己创建一个队列进行异步操作就好了 - 图片异步下载的时候需要用一个
标志
来判断是否下载完成 - 下载的时候使用信号量来处理, 进行阻塞网络请求的那个线程, 等下载所有图片,
signal
一下就把网络请求彻底处理完成了, 此时再将cango
在main async
中修改一为true
- 这里是重点, 使用 Runloop 将主线程阻塞, 但是并不阻塞 UI, 使用方式如下面的测试代码
func test() {
let count = 2
// 展示 loading, 这是我自己封装的可以无视
Toast.showLoading()
(0..<count).forEach { (i) in
// 用于控制主线程是否阻塞用
var cango = false
// 模拟网络请求
GCD.globalAsync {
print("当前第 \(i + 1) 个任务")
(1...3).forEach { k in
sleep(1)
print("等待\(k)秒")
}
let semaphore = DispatchSemaphore(value: 0)
let queue = DispatchQueue(label: "com.mb.study.download.image")
(1...4).forEach { j in
queue.async {
print("下载图片: \(j)")
if j == 4 {
semaphore.signal()
}
}
}
semaphore.wait()
GCD.mainAsync {
cango = true
}
print("completion")
}
while !cango {
print("等待任务完成")
RunLoop.current.run(mode: .default, before: .distantFuture)
}
print("配置完成")
}
print("回归主任务")
Toast.hideLoading()
}
疑惑: 现在这确实可以通透, 但是, 为什么要在 main async 中修改 cango 才能让代码执行下去, 否则会卡在 while 那里, 直到触碰了屏幕(其实就是回到主线程)才可以继续任务, 这个我不清楚, 如有有更好的设计或者直到原因的大佬希望可以为我解惑, 谢谢~
网友评论