现如今,几乎我们的app都需要和服务端进行网络通信。但是,如果我们像处理用户交互一样处理网络通信,整个UI就很容易被卡住,例如我们当前这个例子:
点击“Download”按钮后,它会从泊学服务器下载一些图片,为了演示UI交互效果,我们在屏幕底端放了一个Slider,可以看到当app下载图片时,这个Slider是无法滑动的。这就是我们要使用多线程的一个典型的场景:为App提供更好的用户交互,同时多线程也可以更好的发挥处理器的多核性能。
为了解决这个问题,我们要先多了解一下UI被卡住的这个现象背后的故事:
对于每一个app来说,都有一个主线程用来处理界面更新、响应用户交互等事件,这些事件发生时,它们就会被放到一个由主线程处理的事件队列里由主线程按照发生的顺序依次处理它们。而当我们点击“Download”按钮之后,主线程就会忙于处理下载图片,其他的UI交互事件只能暂时等在队列里。因此,这个时候UI是没有响应的。而当下载完成后,主线程发现还有一个Slider拖动的事件,这时,他就会把Slider移动到相应的位置。
当然,这种体验是非常糟糕的,因此,我们不要把任何网络IO的代码放到一个app的主线程中队列执行。我们将通过一系列视频向大家介绍各种能“并行”处理任务的方法。首先,我们先来看如何使用<key style="box-sizing: border-box;">iOS</key>的“串行队列(Serial queue)”。
创建并使用额外的Serial Queue
一个serial queue和主线程使用的队列是非常类似的:
- 它们都只能顺序执行队列中的任务;
- 主线程队列可以和多个serial queue并行执行;
因此,我们只要新创建一个serial queue,然后点击"Download"时,把下载任务放进去就可以了。我们使用下面的方式创建一个自己的serial queue:
// Create a serial queue
let serialQueue1 = dispatch_queue_create("com.boxueio.images", DISPATCH_QUEUE_SERIAL)
其中,dispatch_queue_create第一个参数是serial queue的名字,第二个参数用来告诉<key style="box-sizing: border-box;">iOS</key>我们要创建一个serial queue。
创建好之后,我们使用dispatch_async,把任务添加到serial queue:
dispatch_async(serialQueue1, {
let img1 =
Downloader.downloadImageWithURL(self.imageUrls[0])
self.image1.image = img1
self.image1.clipsToBounds = true
// Omit for simplicity...
})
dispatch_async的第一个参数是要插入的队列,第二个参数是一个()->Void类型的closure,表示要插入到队列的任务。然后Command + R重新编译执行,点击“Download”按钮,这次UI不会被卡住了,但是图片也显示不出来了,这是为什么呢?
在主线程中更新UI
这是因为所有更新UI的操作,必须在主线程中完成。因此,我们在执行serial queue的线程中更新UI,是不会被显示的。修正这个问题很简单,我们只要把更新UI的操作,放到主线程队列就可以了。
首先,我们使用dispatch_get_main_queue获得主线程队列。然后,用类似的方法把更新UI的操作放到主线程队列里:
dispatch_async(serialQueue1, {
let img1 =
Downloader.downloadImageWithURL(self.imageUrls[0])
dispatch_async(dispatch_get_main_queue(), {
self.image1.image = img1
self.image1.clipsToBounds = true
})
// Omit for simplicity...
})
这时,重新Command + R编译执行,就会发现图片被依次下载下来的同时,我们的UI也始终保持响应了。
使用多个serial queue
我们还可以把图片放在不同的serial queue里下载,这样,虽然在一个serial queue内部任务是串行执行的,但多个serial queue之间,是并行执行的。我们把第1和第3张图片放在一个队列里,第2和第4放在另外一个队列里。
// Add task
let serialQueue1 =
dispatch_queue_create("com.boxueio.images",
DISPATCH_QUEUE_SERIAL)
dispatch_async(serialQueue1, {
let img1 =
Downloader.downloadImageWithURL(self.imageUrls[0])
dispatch_async(dispatch_get_main_queue(), {
self.image1.image = img1
self.image1.clipsToBounds = true
})
// Omit for simplicity...
})
let serialQueue2 =
dispatch_queue_create("com.boxueio.images1",
DISPATCH_QUEUE_SERIAL)
dispatch_async(serialQueue2, {
let img2 =
Downloader.downloadImageWithURL(self.imageUrls[1])
dispatch_async(dispatch_get_main_queue(), {
self.image2.image = img2
self.image2.clipsToBounds = true
})
// Omit for simplicity...
})
这时,重新Command + R编译执行,就会发现图片被依次下载下来的同时,我们的UI也始终保持响应了。