【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记31 Multithreading多线程

简介: 在IOS中存在着许多队列,和我们数据结构中的队列一样,这里的队列概念也是先进先出的。而每一个方法(包括闭包)都被组织在这些不同的队列中,而每一个队列都有自己的线程去运行这些队列,这就造就了多线程环境。


在IOS中存在着许多队列,和我们数据结构中的队列一样,这里的队列概念也是先进先出的。而每一个方法(包括闭包)都被组织在这些不同的队列中,而每一个队列都有自己的线程去运行这些队列,这就造就了多线程环境。

其中有一个非常重要的队列叫做主队列,主队列是一个串行队列,所以主队列只会一个一个地执行主队列中的函数。所有的UI活动都必须发生在主队列中,所以当你想要一个函数或者是闭包的时候就会执行某些代码,这就会做任何关于UI的事,必须把它放到主队列中,这是保护UI的好办法,主队列绝对不想做任何可能被阻塞的事情,比如读取一个包含URL的NSData,上一话中的Demo当我们点击按钮获取大图的时候会卡顿,所以我们要把它从主队列中拿出来。

通常你使用MVC中的东西都在主队列中,比如页面生命周期中的ViewDidLoad、viewWillAppear,我们不需要特别的留心。

IOS会在你需要的时候为你创建其他队列。

那么如何在其他队列中执行函数或者闭包呢?


首先初始化一个队列,然后调用了函数dispatch_async,有两个参数,第一个是队列,第二个是一个闭包,这里写成了尾随闭包的形式。那么第一句中如何获得主队列呢?使用函数dispatch_get_main_queue(),就能返回主队列了。此外还有一个面向对象的方法可以做到:NSOperationQueue.mainQueue()。上面有一个例子,在其他队列中执行一些可能阻塞UI的操作,然后打开主队列,执行需要呈现给UI的东西。

下面介绍一些主队列之外的其他队列,这些队列通常使用不同的处理级别来表示,处理级别的高低也代表了队列不同的处理速度。


要创建一个非主队列,首先要创建一个服务级别,创建级别的方法有点奇怪,使用了一个Int的构造器,构造其中选择级别的value属性,这种做法是由历史原因造成的。在创建好级别之后,使用dispatch_get_global_queue函数,传入级别和一个0,这个0以后会用到。有了新的队列,你就可以使用dispatch_asnyc函数并且在非主队列中执行代码了。

如果有多核处理器,队列真的可以并行运行,但是大部分情况下他们是分时的。你可以创建自己的串行队列,使用dispatch_queue_create来建立一个串行的队列。

在iOS中有些API是多线程的,比如下面这个例子:


比如上例中的NSURLSession这个类,注意黄色字体的部分,它让你从一个URL中下载一个文件,而且是异步的去做。在方法后面增加了一个闭包,这个闭包的作用是当你下载完毕时你需要在UI中打开这个文件,闭包为你指定了一个本地URL,一个HTTP应答了一个错误信息,利用这些信息你会做一些更新UI的事情。那么这样做行得通么?答案是“NO”。

因为这个下载的文件不在主队列中,解决办法是在闭包中打开主队列:


现在来进行实战环节,回到我们之前的Demo中,打开图片总是会造成阻塞,这并不好,尝试使用多线程的知识来解决这个问题。

我们需要做的是把获取图片的环节放到其他线程中,方法fetchImage修改如下:

 func fetchImage(){
        if let url = imageURL {
        let qos = Int(QOS_CLASS_USER_INITIATED.value)//指定服务“可能花费时间,但是用户需要得到,尽快完成”
        dispatch_async(dispatch_get_global_queue(qos, 0)){ () ->Void in//把要执行的代码放到闭包中
        let imageData = NSData(contentsOfURL: url)
        if imageData != nil{
        self.image = UIImage(data: imageData!)
        } else {
        self.image = nil
        }
            }
        }
    }
注意现在还不够,设置image的动作会修改UI,所以这个动作应该在主队列中,我们把设置image的动作放到主队列中:

 func fetchImage(){
        if let url = imageURL {
        let qos = Int(QOS_CLASS_USER_INITIATED.value)//指定服务“可能花费时间,但是用户需要得到,尽快完成”
        dispatch_async(dispatch_get_global_queue(qos, 0)){ () ->Void in//把要执行的代码放到闭包中
        let imageData = NSData(contentsOfURL: url)//加载图片应该放到其他线程中
            dispatch_async(dispatch_get_main_queue()){//把加载到的图片显示在页面中的时候使用主线程
        if imageData != nil{
        self.image = UIImage(data: imageData!)
        } else {
        self.image = nil
        }
            }
            }
        }
    }
现在加载图片的时候系统不会卡顿,你会跳转到下一个页面中,在当前页面中等待即可看到加载完的图片:

现在的问题是每一次点击一个按钮都会生成一个全新的MVC,那么如何避免重复加载呢,我们需要判断一下点击按钮时获取的URL是否是当前页面上图片的URL,方法修改如下:

  func fetchImage(){
        if let url = imageURL {
        let qos = Int(QOS_CLASS_USER_INITIATED.value)//指定服务“可能花费时间,但是用户需要得到,尽快完成”
        dispatch_async(dispatch_get_global_queue(qos, 0)){ () ->Void in//把要执行的代码放到闭包中
        let imageData = NSData(contentsOfURL: url)//加载图片应该放到其他线程中
            dispatch_async(dispatch_get_main_queue()){//把加载到的图片显示在页面中的时候使用主线程
                if url == self.imageURL{//新增判断语句
        if imageData != nil{
        self.image = UIImage(data: imageData!)
        } else {
        self.image = nil
        }
            }
            }
            }
        }
    }

最后需要做的是在其他队列加载图片的时候,主页面上运行一个齿轮来表示这个加载过程。

现在去对象库中拖出一个齿轮控件到场景中,打开文档大纲你会发现这个新增的齿轮控件被加到了scrollview中,这是因为我们之前用这个scrollview几乎铺满了我们的view:

这里文档大纲的优势就体现出来了,你可以在文档大纲中拖动控件来组合它们的次序,下面是正确地顺序:

另外在文档大纲中可以增加约束,和在IB中一样,按住control拖动即可,我们让齿轮控件居中


然后设置齿轮控件的属性,勾选当它停止时消失的选项:


和控制器建立连接:

@IBOutlet weak var spinner: UIActivityIndicatorView!
当我建立一个HTTP请求的时候让这个齿轮转动,当我结束这个请求的时候,让齿轮停止转动。

所以很明显我们需要在下面的位置增加齿轮的转动:

 func fetchImage(){
        if let url = imageURL {
            spinner?.startAnimating()//新增齿轮转动
        let qos = Int(QOS_CLASS_USER_INITIATED.value)//指定服务“可能花费时间,但是用户需要得到,尽快完成”
        dispatch_async(dispatch_get_global_queue(qos, 0)){ () ->Void in//把要执行的代码放到闭包中
        let imageData = NSData(contentsOfURL: url)//加载图片应该放到其他线程中
            dispatch_async(dispatch_get_main_queue()){//把加载到的图片显示在页面中的时候使用主线程
                if url == self.imageURL{//新增判断语句
        if imageData != nil{
        self.image = UIImage(data: imageData!)
        } else {
        self.image = nil
        }
            }
            }
            }
        }
    }

注意在调用spinner的时候把它当做可选型,因为页面可能先于spinner控件生成,在调用控件的时候这是个常用的办法。但是这个方法中加载完毕有不同的处理情况,共同点是都要处理image,所以我们选择在计算属性image的set方法中停止齿轮:

private var image:UIImage? {
        get {return imageView.image}
        set {
        imageView.image = newValue
        imageView.sizeToFit()
        scrollview?.contentSize = imageView.frame.size
        spinner?.stopAnimating()//加载完成了停止齿轮
        }
    }

现在来运行时候,当图片没有加载完成的时候,之前我们看到的是一个白色的页面,现在页面中间有齿轮在转动了:


加载完毕后齿轮消失:



目录
相关文章
|
前端开发 API
【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记4 MVC enum Tuple Dictionary
 回顾一下我们上一话中的代码: @IBAction func operate(sender: UIButton) { let operation = sender.
790 0
|
前端开发 iOS开发
【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记3 Xcode、Auto Layout及MVC
   继续上一话中的计算器Demo,上一话讲到类必须被初始化,类中的属性也必须被初始化,所以你不能只声明而不给它一个处置,那么问题来了,我们从storyboard中拖拽的@IBOutlet为什么只有声明而不需要初始化呢,这是因为它的类型依旧是一个optional,在你初始化之前已经被赋值为nil了,这也就是为什么你不需要再初始化它的原因。
873 0
|
Unix iOS开发
【我们都爱Paul Hegarty】斯坦福IOS8公开课个人笔记1 IOS8概述
  首先感谢网易公开课和SwiftV课堂的朋友们辛苦翻译,这个系列是我学习斯坦福IOS8公开课的个人心得体会和笔记,希望能给大家带来启发。
1061 0
|
11天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
2月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
118 1
|
2天前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
4天前
|
存储 前端开发 Swift
探索iOS开发:从新手到专家的旅程
本文将带您领略iOS开发的奇妙之旅,从基础概念的理解到高级技巧的掌握,逐步深入iOS的世界。文章不仅分享技术知识,还鼓励读者在编程之路上保持好奇心和创新精神,实现个人成长与技术突破。
|
18天前
|
安全 数据处理 Swift
深入探索iOS开发中的Swift语言特性
本文旨在为开发者提供对Swift语言在iOS平台开发的深度理解,涵盖从基础语法到高级特性的全面分析。通过具体案例和代码示例,揭示Swift如何简化编程过程、提高代码效率,并促进iOS应用的创新。文章不仅适合初学者作为入门指南,也适合有经验的开发者深化对Swift语言的认识。
39 9
|
14天前
|
设计模式 Swift iOS开发
探索iOS开发:从基础到高级,打造你的第一款App
【10月更文挑战第40天】在这个数字时代,掌握移动应用开发已成为许多技术爱好者的梦想。本文将带你走进iOS开发的世界,从最基础的概念出发,逐步深入到高级功能实现,最终指导你完成自己的第一款App。无论你是编程新手还是有志于扩展技能的开发者,这篇文章都将为你提供一条清晰的学习路径。让我们一起开始这段旅程吧!
|
18天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。