Swift多线程:使用GCD实现异步下载图片

huaaijia 3年前
   <p>GCD属于系统及的线程管理,功能很强大,比上两次咱们分享的Operation要强大。有很多老前辈们已经创造了非常非常多的资料介绍GCD,因为大家都是把GCD放在了多线程内容分享的最开始,所以导致好多好多理论知识都被放在了GCD部分。</p>    <p>哈哈~幸好非典型技术宅英明神武的错峰出行,把一些基础概念放在了上两篇文章里面。极大的减轻了这篇文章的阅读负担。</p>    <p>既然前人都早了辣么多轮子,俺就不想再多介绍一些基础理论知识了。反正码再多的字,只会让大家立刻马上关掉这篇文章。而且上一篇关于Operation的阅读量就明显不高,看来大家不喜欢看啊。。。</p>    <p>那就容我偷偷懒嘛~重点还是分享一些代码吧。</p>    <p>不是说理论知识不重要啊,面试全都问这个。而且理论知识直接影响到对技术的理解深度,决定能在这条路上走多远。是会成为某个领域的大牛,还是只是简单的应用者。</p>    <h2>1. GCD基础知识</h2>    <p>纳尼?不是说不说基本概念了吗?easy~easy~~easy~~~只介绍一些那些最最重要的,不了解就会影响到阅读这篇文章的内容啦。</p>    <p>其实GCD和Operation很多地方惊人的相似。废话,都是多线程,底层都差不多,能不相似嘛!</p>    <p>GCD使用只需要两步:</p>    <ul>     <li> <p>STEP ONE:创建任务。</p> </li>     <li> <p>STEP TWO:把任务放进队列里。</p> </li>    </ul>    <p>。。。。。。~!@#¥%……&*¥%#@!~@#¥%…… 把大象放进冰箱里需要几步?!两步!打开冰箱门,把大象放进去!宅胖,现在很想抽死你啊!</p>    <p>确实真的就是这样的这只是为了骗你入门,让你觉得好简单。</p>    <h3>1.1 任务的分类</h3>    <p>上面说了任务,任务只有两种方式:同步、异步。</p>    <ul>     <li> <p>异步(asynchronous)具备开启新线程的能力,也具备跳过当前代码继续往下执行的能力。</p> </li>     <li> <p>同步(synchronous)不具备开启新线程的能力,也不具备跳过当前代码继续往下执行的能力。</p> </li>    </ul>    <p style="text-align:center"><img src="https://simg.open-open.com/show/bf95efdde2726cb4cb821214845aa6ee.png"></p>    <p>换句话简单的说,异步任务就是可以同时开启多个跑道,同时跑好多辆车。同步就是只有一条车道,堵死也飞不过去,只能乖乖的等着,一辆接一辆。</p>    <p>任务放入到队列里面,会遵循first in first out原则。举个恶心的例子,就像是吃饭后呕吐,后吃进去的先吐出来,先吃的东西后吐出来。</p>    <p>哈哈~看了这个比方,别打死我~</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/7ca1d5d176bca5c0a6fff953474f185e.png"></p>    <h3>1.2 队列的分类</h3>    <p>队列呐,也只有两种:串行队列(Serial Dispatch Queue)、并发队列(Concurrent Dispatch Queue)。</p>    <ul>     <li> <p>串行队列(Serial Dispatch Queue):</p> </li>    </ul>    <p>让任务一个接着一个有序的执行,一个任务执行完毕后,再执行下一个任务。</p>    <ul>     <li> <p>并发队列(Concurrent Dispatch Queue)</p> </li>    </ul>    <p>可以让多个任务同时执行,自动开启多个线程同时执行多个任务。</p>    <p>咦?有点晕,怎么感觉跟刚才的任务分类一样呐?没错!就是这样的。</p>    <p>下面为了让大家不要晕菜,我们把队列这个中文名字统一都叫做Queue,这样就和OperationQueue对应起来了,就不会那么晕了。</p>    <p>Serial Queue和Concurrent Queue各自都有一个特殊的Queue。</p>    <ul>     <li> <p>主队列(main queue):是Serial Queue中特殊的一种。只能在主线程中进行,并且主队列里面的任务,只有当主线程空闲的时候才能被执行。用来刷新UI使用。</p> </li>     <li> <p>全局队列(global queue):是Concurrent Queue中特殊的一种。用来执行耗时操作。</p> </li>    </ul>    <p>同时,GCD里面还可以自定义Queue。</p>    <h3>1.3 排列组合开始</h3>    <p>最开始的时候,咱们是不是说了,使用GCD就只有两步:创建任务,把任务放进Queue里。</p>    <p>任务有两种:同步、异步。Queue加上两种特殊的(不包括自定义的)一共有四种。来吧,开始排列组合吧。有八种吧。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/3dcea1d9a144e2333a73812051803293.png"></p>    <p>哈哈哈O(∩_∩)O哈哈~????????????????</p>    <p>彻底晕菜????</p>    <p>oooO ↘┏━┓ ↙ Oooo</p>    <p>( 踩)→┃你┃ ←(死 )</p>    <p>\ ( →┃√┃ ← ) /</p>    <p>_)↗┗━┛ ↖(_/</p>    <p>来吧,直接告诉你结论吧。里面有几个特例。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c9d9d0e0f8561679d9caaea8972db889.png"></p>    <p>看上面这个表,所以如果想要同时做事情,当然不能选同步任务啦。因为它完全没能力!搞不好还会造成锁死。</p>    <p>要想同时做事情,就选concurrent Queue + 异步,或者global Queue + 异步。 不过人家global Queue本来就是concurrent Queue特殊的一种。</p>    <p>如果有多任务,工作中最最省事儿常用的就是global Queue + 异步。单任务、刷新UI就用main Queue + 异步。</p>    <p>上面都没心思看也没关系。工作中,如果有多任务,首选global Queue + 异步。单任务、刷新UI就用main Queue + 异步。</p>    <h2>2. GCD的基础应用</h2>    <p>我滴妈妈~经过上面的分析,最后,最基础的使用就两种了。</p>    <p>多任务:global Queue + 异步。</p>    <p>单任务、刷新UI就用main Queue + 异步。</p>    <p>说实话,我也是第一次这么大胆的简化。会不会被大神们拍死?坐等~~~~</p>    <h3>2.1 global Queue + 异步任务</h3>    <pre>  <code class="language-swift">/// global Queue + 异步任务  @IBAction func globalAsyn(_ sender: Any) {      //创建一个全局队列。      //get a global queue      let globalQueue = DispatchQueue.global()      for i in 0...10 {                    //使用全局队列,开启异步任务。          //use the global queue , run in asynchronous          globalQueue.async {              print("I am No.\(i), current thread name is:\(Thread.current)")          }      }  }</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/630fc0534e6435d85a1a7a048eb962b8.png"></p>    <p>我们看一下运行的结果,乱序打印的,并且没有在主线程中。这证明了确实是多个任务没有按照顺序执行。</p>    <h3>2.2 main Queue + 异步任务</h3>    <pre>  <code class="language-swift">/// main Queue + 异步任务  @IBAction func mainAsyn(_ sender: Any) {      //创建一个主队列      //get a main queue      let mainQueue = DispatchQueue.main            for i in 0...10 {                    //使用主队列,开启异步任务          //use the main queue, run in asynchronous          mainQueue.async {              print("I am No.\(i), current thread name is:\(Thread.current)")                        }      }  }</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/b31656daabed4595a444674a45db6a80.png"></p>    <p>我们看一下运行的结果,确实是顺序打印的。并且都执行在了主线程中。</p>    <h3>2.3 小实践:实现异步下载图片</h3>    <p>需求:异步下载一张图片,下载完成后显示在UI界面</p>    <p>实现后的效果图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/8b19a31aab88434702e6786311aba00d.gif"></p>    <p>思路:</p>    <ol>     <li> <p>在当前UI动作之外,开启一个global Queue+异步,用来下载图片。因为过程可能很耗时。</p> </li>     <li> <p>等下载完成后,开启一个main Queue+异步,把下载的图片赋值,刷新UI。</p> </li>    </ol>    <p>这个小Demo其实也实现了线程间通讯。</p>    <pre>  <code class="language-swift">@IBAction func asynDownloadImage(_ sender: Any) {      let imageVC = ImageVC()      DispatchQueue.global().async {                    if let url = URL.init(string: "https://placebeard.it/355/140") {              do {                  let imageData = try Data(contentsOf: url)                  let image = UIImage(data: imageData)                                      //因为宅胖家网络很好,为了模拟网络很耗时,就用了延时加载。                  DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(2), execute: {                      imageVC.imageView.image = image                      imageVC.imageView .sizeToFit()                  })                                } catch {                  print(error)              }          }                }      navigationController?.pushViewController(imageVC, animated: true)    }</code></pre>    <h2>3. GCD的服务质量(优先级)</h2>    <p>DispatchQoS.QoSClass是在Swift中封装的关于描述服务质量的类。</p>    <p>这个在Operation里面也见到过,级别越高,就会给分配的资源越多。但是并不是严格按照级别的高低来执行的。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/40e2ba5ab8c9f286a63beb00938d1c66.png"></p>    <p>这是一个枚举值:</p>    <pre>  <code class="language-swift">public enum QoSClass {        case background  //后台默默执行,The background quality of service class.      case utility  //通用的,The utility quality of service class.        case `default` //默认值,The default quality of service class.          case userInitiated  //用户发起的,The user-initiated quality of service class.          case userInteractive //用来执行用户交互,The user-interactive quality of service class.          case unspecified //没啥重要事情,The absence of a quality of service class.          public init?(rawValue: qos_class_t)        public var rawValue: qos_class_t { get }  }</code></pre>    <p>看到上面的枚举值,也大概能猜出来优先级的高低了。和界面相关的、用户的肯定是高的,后台默默执行的肯定是低的。</p>    <p>从高到低的顺序分别是:userInteractive ->  userInitiated -> default ->  utility -> background -> unspecified</p>    <p>最基本的基础基本上就到这里了。掂量了一下,还有调度组、信号量、阻塞等等都还没写。这时候发现一篇写完GCD基础貌似不太现实,又不想一篇文章过长,那就拆开吧。下次再说。</p>    <ul>     <li> <p><a href="/misc/goto?guid=4959750960636296469" rel="nofollow,noindex">iOS多线程系列之一:Operation基础操作,按优先级加载图片</a></p> </li>     <li> <p><a href="/misc/goto?guid=4959751132158497870" rel="nofollow,noindex">iOS多线程系列之二:Operation实例,异步加载CollectionView图片</a></p> </li>     <li> <p><a href="/misc/goto?guid=4959751132247663149" rel="nofollow,noindex">iOS多线程系列之三:使用GCD实现异步下载图片</a></p> </li>    </ul>    <p>来自:http://www.jianshu.com/p/de4c990f64e9</p>    <p> </p>