美文网首页
斯坦福公开课Swift笔记 10:多线程GCD(MultiThr

斯坦福公开课Swift笔记 10:多线程GCD(MultiThr

作者: CyberDunk1997 | 来源:发表于2020-07-27 22:11 被阅读0次

    1. 队列

    • 多线程很大程度上都与iOS的队列有关
    • 函数或方法(通常是闭包)会被排成一个队列来执行,而这些方法会被放进队列中,并且会在一个与之关联的线程中被执行
    • 队列可以是串行的“serial”(一次只执行一个闭包),也可以是并行的“concurrent”(多个线程同时在为他服务),多核cpu可以并列执行任务,单核cpu也能通过细分时间来实现并行。

    2. 主队列

    • 主队列是一个很特殊的串行队列,所有和UI相关的活动,都发生在主队列中,而且也只能发生在主队列中。
    • 相反的,一些非UI操作,且十分耗时的活动,不应该出现在主线程中!原因:我们希望用户的UI操作是高度可响应的。举个例子,加入用户点击了下载图片按钮,在图片下载完成之前,用户的UI界面不应该被卡住,而是可操作,用户可以决定是否要返回上一级,或者退出当前菜单,甚至取消下载,而不是一个等待的菊花不停旋转。
    • 注意:当我们在其他非主队列进行操作后,如果想对UI进行操作,必须先返回主队列,再进行更新UI

    3. 全局队列

    • 对于一些不在主队列执行的任务,通常会用共享的,全局的,并发的队列

    4. iOS中如何获得队列?

    4.1 获得一个队列

    //1. 获得一个主队列
    let mainQueue = DispatchQueue.main
    //2. 获得全局,并发,在后台运行的队列
    let backgroudQueue = DispatchQueue.global(qos : DispatchQos)
    

    DispatchQueue.userInteractive // 高优先级,适合又短又快的任务,很少用,可以在主线程上做
    DispatchQueue.userInitiated // 高优先级,适合有一点耗时的任务,比较常用
    DispatchQueue.backgroud // 低优先级,不直接被用户创建,所以可以做很耗时的任务
    DispatchQueue.utility // 低优先级,长时间的后台处理

    4.2 在队列中添加代码

    • 通常用闭包在多线程中添加代码,通常有两种添加代码的方式
    1. 异步执行:保持当前队列运行,并在另一条队列中添加任务
      queue.async { ... }
    
    1. 同步执行:阻止当前队列运行,等待加入队列中的任务完成后再继续执行
      queue.sync { ... }
    
    • 通常情况下,async使用的更多

    4.3 创建你自己的队列

    • 在极少数情况下,我们需要一些既不是主队列,也不是全局队列的队列
    //创建一个自己的串行队列
    let serialQueue = DispatchQueue(label: "MySerialQ")
    //创建一个自己的并行队列
    let concurrentQueue = DispatchQueue(label: "MyConcurrentQ", attributes: .concurrent)
    

    4.4 OperationQueue 和 Operation

    • 在需要做一些复杂的多线程任务时,比如任务C依赖任务A和B,而任务D又依赖任务A和C,Operation API 会起到很大的作用,它能提供不同线程的依赖关系。

    4.5 iOS API中的多线程

    • 下面这个iOS的API会让我们跳出主队列,进行数据的获取
    let session = URLSession(configuration: .default)
    if let url = URL(string: "http://www.baidu.com") {
      let task = session.dataTask(with:url) { (data: Data? , response , error ) in 
      // **在这里能用下载的data来更新我的UI界面的吗?**
      xxx...
      }
      task.resume()
    }
    
    • 在上面dataTask的尾随闭包中,可以用下载的data来更新我的UI界面吗?
    • 答案是:不行。因为在这个闭包中,代码并非在主队列中执行,UI不能再非主队列之外的地方执行。
    • 解决方案: 只需要返回主队列,再进行更新UI就可以了
    let session = URLSession(configuration: .default)
    if let url = URL(string: "http://www.baidu.com") {
      let task = session.dataTask(with:url) { (data: Data? , response , error ) in 
        Dispatch.main.async {
          // 这里可以更新UI啦!
        }
      }
      task.resume()
    }
    

    4.6 异步执行顺序

    异步执行顺序.png
    1. 第一步:a。将String转换成url。
    2. 第二步:b。这一行代码除了创建了一个dataTask并把它传给task之外什么也没做,b行被执行后会立即返回,等待数据下载完成后才执行闭包里的代码。
    3. 第三步:g。g做的是在其他队列中获取数据。g行在b行后会被立即执行,也同时会立即返回。
    4. 第四步:h。打印“已经发起了url请求”。这时已经跳出了第一个大括号。a→b→g→h被执行,它们是一个接一个地被执行,中间没有任何停顿,而c,d,e,f行会在其他另一个队列中被执行。
    5. 第五步:c。在几秒钟,或者是几分钟,甚至时几小时后,数据终于返回,c行被执行,在这里可以进行处理数据
    6. 第六步:d。在这里发起返回主队列的异步请求。由于主队列可能正在进行其他任务,所以不一定会立即响应,e不会被立即执行。
    7. 第七步:f。打印“做一些处理data数据的工作,但此时UI还没有更新”。
    8. 第八步:e。等待主队列没有任务在执行了,终于轮到e执行,可以更新UI了。
    • 总结:a→b→g→h→c→d→f→e。但是这不是唯一的可能性。因为当d执行后,主队列可能正好是空闲的,所以会立即执行e,此时e和f是同时进行的,所以也可能发生a→b→g→h→c→d→e→f。

    请求图片的实例代码

    if let url = imageURL { // 获取一个url地址
      DispatchQueue.global(qos: .userInitiated).async { //在全局并发线程中,发起一个异步请求
      let urlContents = try ? Data(contentsOf: url) //用url地址去发起请求,将返回结果赋值给urlContents
      if let imageData = urlContents { // 如果返回的内容非空...,赋值给imageData 
        self.image = UIImage(data: imageData) //给self.image赋值
        }
      }
    }
    
    • 注意:当用户在还没有下载完成之前离开了界面,即用户不再需要这个数据,数据下载便没有了意义,这个数据和这个ViewController都不应该继续存在于堆中。但是由于上面代码的闭包中self.image一直指向ViewController,导致ViewController一直被保存在堆中,这就导致了资源的浪费。
    • 解决方法:通过【weak self】,弱引用self,当用户退出界面,没有指针指向self,此时【weak self】闭包中的self也被设为nil
    if let url = imageURL {
      DispatchQueue.global(qos: .userInitiated).async { *** [ weak self ]  in *** //注意这里
      let urlContents = try ? Data(contentsOf: url)
      if let imageData = urlContents { 
        self?.image = UIImage(data: imageData) //self.image 变成了self?.image, 当self不存在时,不执行
        }
      }
    }
    
    • 当然上面的代码仍然有问题,因为self?.image = UIImage(data: imageData)涉及到更新UI事件,而上面代码这句话并不是在主队列中执行的,所以需要回到主队列,代码如下
    • 问题2,加入下载数据需要花费5分钟,五分钟后下载的数据并不是我当前需要的数据了,那也不需要更新UI了,所以需要在设置UI之前加一次判断
    if let url = imageURL {
      DispatchQueue.global(qos: .userInitiated).async { *** [ weak self ]  in *** //注意这里
      let urlContents = try ? Data(contentsOf: url)
        DispatchQueue.main.async { //回到主队列
          if let imageData = urlContents ,url == self?.imageURL { //加一层判断,保证返回的数据是我所需要的
          self?.image = UIImage(data: imageData) //self.image 变成了self?.image, 当self不存在时,不执行
          }
        }
      }
    }
    

    相关文章

      网友评论

          本文标题:斯坦福公开课Swift笔记 10:多线程GCD(MultiThr

          本文链接:https://www.haomeiwen.com/subject/kmptrktx.html