因为线程是串行的,这意味着如果我们要下载一个图片,使它呈现在一个
UIImageView
里,那么这个线程必须要等待图片下载完成后,才能显示出来。为了解决这样的问题,我们需要用到多线程的技术,将下载图片交给子线程完成,将加载图片到UIImageView
交给主线程,这就涉及到一个问题,就是线程之间的通信。
线程通讯的两种方式
- 从一个线程给另一个线程传递数据。
- 在一个线程中执行完特定任务后,转到另一个线程继续执行。
以上面的问题举例,我们要在一个线程中下载图片,然后要另一个线程展示图片,苹果官方推荐,刷新界面的操作最好在主线程中完成,那么我们来实验一下,如果将展示图片的操作交给子线程,会发生什么。首先我们创建一个 UIImageView
并将它添加到 UIViewController
,然后我们创建一个子线程:
|
|
下面我们创建 run
这个线程执行方法,写入以下代码下载图片:
|
|
最后将图片加载到 UIImageView
里:
|
|
现在我们运行这个程序,如果你的 XCode 版本是新的话,那么可能会收到一条报错:
|
|
这个原因是 iOS9 中新增了 App Transport Security
(简称ATS)特性,在 iOS9 以前请求网络的时候都用到的HTTP
,现在转向TLS1.2协议进行传输。这也意味着所有的 HTTP
协议都强制使用了 HTTPS
协议进行传输,所以系统会告诉我们不能直接使用HTTP进行请求,这需要在 Info.plist
新增一段用于控制ATS的配置:
|
|
我们重新运行这个程序,相信这时图片已经成功加载出来了,但是控制台会收到这样一句提示:
|
|
这是说,我们使用了 background thread
后台进程更改了布局,这可能会导致崩溃和其他问题,这也符合苹果官方所推荐的:刷新界面的操作最好在主线程中完成。
那么现在我们尝试解决这个问题,将刷新界面的操作交给主线程操作,相信大家已经意识到问题的核心,如何通知主线程刷新界面 ?我们来调用这个方法:
|
|
下面我们先看一下这个方法的参数:
形参 | 作用 | 填写 |
---|---|---|
aSelector: Selector | 这是一个选择器,代表要执行的方法。 | 自己写的方法 |
withObject arg: AnyObject? | 这是执行方法的传入参数。 | 可以是nil |
waitUntilDone wait: Bool | 是否要等待执行方法的完成。 | 可以是false |
前两个参数比较好理解,因为执行方法也是需要时间的,第三个参数 true
和false
的区别是:如果是 true
的话,主线程会等待执行方法完成,主线程才会继续其他的操作;如是是 false
的话,主线程马上就会结束,并不关心执行方法是否完成。
如果你有一些限制,比如要等执行方法完成之后,在执行一些其他的工作,这是就输入 true
,如果你不关心执行的结果,就输入 false
。
下面我们实现让主线程操作 UIImageView
显示图片的方法:
|
|
再次运行,风险提示没有了,这说明我们实现了,从子线程中下载图片,并调用主线程,在主线程中刷新界面这样一个功能。