ios多线程
2015-12-21 12:44
375 查看
关于多线程处理,很多人不敢用,因为多线程常常会导致一些奇葩的问题出现(大多都是资源共享导致的)。但存在肯定是有其道理的,如果你对它足够了解,当然也不一定就不出问题。起码是出了问题很快就可以定位到。至于多线程的优势,我想不说大家也都知道,肯定是比单线程要快的。这里就讲讲iOS的多线程并发处理。现在的设备很少是单核处理器了,如果你还用单线程,那可真是暴殄天物啊。那么多资源就被你这样浪费了。在iOS中主线程是处理UI的,你如果什么都在主线程里操作,这样用户的体验会非常的差,因为操作一个事件就要等其结束才能继续。所以用户体验很差,今天就来谈谈iOS的多线程处理。
第一部分:GCD
GCD是管理多线程最常用的api,GCD提供并管理任务队列。当然首先要了解队列是什么东西。
队列是管理对象的先进先出(FIFO)的一种数据结构,就像排队买票,排在前面的先买,后面的后买一样。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202009/25/33d7c26d2f63bab4e50a6ececed5e66b)
队列分为两种队列,一种就是分发队列(Dispatch queues)一种是顺序队列(Serial queues)
分发队列是以一种比较简单的方式在应用中来执行的异步并发任务,分发队列的任务是以block的形式提交的,无论是分发队列还是顺序队列,都是在主线程提交任务,其他线程来执行的队列。分发队列是以平行的并发顺序执行任务的。顺序队列是有先后顺序的(避免资源被竞争)。
在iOS中系统提供给每个应用一个顺序的队列和四个并发队列。其中主线程(顺序队列)来执行UI操作,所以如果大量的任务都放到主线程,那么主线程就会被阻塞掉。除了主线程队列,系统还提供了四个并发队列,我们称之为全局分发队列。这些队列是全局的队列,他们仅仅是优先级的区别。为了使用这些队列,你需要使用这个functiondispatch_get_global_queue函数,并设置第一个参数(参数如下)来得到这个队列的引用。
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND
当然,他们的优先级从上到下越来越低的是。你可以使用任意一个你想用的队列,但是请注意,这些队列是系统提供给你的,所以队列里面的任务不一定都是你的任务(可能有其他的任务)。
GCD快速预览
了解了队列的基础知识之后,我们来看看什么是GCD
![](https://oscdn.geek-share.com/Uploads/Images/Content/202009/25/cb7f117c69a6da35728c151ca9166299)
接下来我们来个demo。
Demo Project
我们的项目比较简单,仅仅展示四幅图片,每张图片都根据url从网上请求,图片请求在主线程中,为了向你展示这个会影响UI的响应,我们在图片下面加了个组件slider。demo 在这里下载。点击开始下载图片的start按钮,同时拖拽下面的slider。你会发现,你根本拖动不了它。
![](https://oscdn.geek-share.com/Uploads/Images/Content/202009/25/4117622d391d23fad56d481a1237cb27)
一旦你点击开始按钮,图片就开始下载了在主线程中,很显然,这个方法很糟糕,它使得UI无法响应其他任何事件,不幸的是,至今还有一些应用依然在主线程内执行大量的任务。那么现在我们开始定位这个问题,并用并发队列来解决它。
我们首先用并发队列来实现它,然后再用顺序队列来实现它。(两种方法而已)
运用并发队列
首先打开 ViewController.swift这个文件在Xcode中,看下代码,你就会找到这个方法didClickOnStart,它就是下载图片的方法。
每个下载方法被看作是一个任务,并且所以任务都在主线程内顺序执行,那么现在我们要获取一个全局并发队列的一个引用(默认优先级的就可以了)。
我们得到了一个默认的并发队列通过这个方法dispatch_get_global_queue,并且在内部的block内我们提交了一个下载图片的任务,一旦图片下载完成,我们提交另一个任务给主线程,让其更新下载好的图片,换句话来讲,就是我们把下载图片的方法放到了后台去做,但是执行UI更新还是在主线程内。其他部分也如此,修改后的代码就如下所示:
你提交了四个图片的下载任务(并发的)到默认的队列,重新运行应用时你会发现速度快了很多,并且你也可以拖动那个slider了。
运用顺序队列
另一个可选的方法是使用顺序队列来处理这个问题,同样修改这个ViewController.swift文件的didClickOnStart()方法,这次我们用顺序队列来下载图片。当使用顺序队列的时候,需要注意的是,每个应用有个默认的顺序队列,那就是主队列,用来处理UI的。所以记住,使用顺序队列,你必须自己新建顺序队列来使用,否则你还是在主队列里面操作。并且会导致你要在主队列执行任务,同时试着去更新UI
,可能会报错。你可以使用函数dispatch_queue_create来创建新的队列并且提交任务到新的队列里面去,修改之后,我们的代码就变成如下的样子:
以上的两种方法的不同之处就是顺序队列需要自己去创建。但是以下两点我们仍须注意:
1、顺序队列比并发队列花费的时间长,原因就是顺序队列每次仅下载一张图,任务被顺序的执行。
2、图片被下载顺序是 image1, image2, image3, and image4 ,因为顺序队列每次仅执行一个任务。
第二部分:Operation Queues
GCD是一个底层的C语言API,它允许开发者并发执行任务。而Operation queues是一个高级的抽象的队列模型,并且在GCD的顶端。那就是说你可以执行并发任务像GCD一样,但是是以面向对象的方式。简单的说,Operation
queues就是让开发者的生活更简单了,方法更容易使用了。
它不像GCD一样先进先出的顺序,下面就是它与并发队列的不同之处。
1、不遵循先进先出的队列形式,在Operation queues中,你可以设置操作优先级并且可以设置任务之间的依赖(就是某个任务要在另一个任务执行之后才开始执行的这样的依赖),这就是为什么它不遵循先进先出的原则。
2、默认的也是并发执行的,你不能改变它(不能变成顺序队列),但你可以设置依赖关系。
3、Operation queue是这个类NSOperationQueue的一个实例。它的任务被封装在NSOperation实例中。
任务是以NSOperation的实例的形式被提交到operation queues中的,我们上面讨论的GCD的任务是以block的形式提交的。这里也是一样的道理,我们是以NSOperation的实例作为一个基础单元。NSOperation是一个抽象的类,不能被直接实例化,在iOS中我们提供了两个子类来实例化,这两个类可以直接使用,但并不拘于此,你也可以自己来继承NSOperation它并实例化它。两个子类如下:
1、NSBlockOperation –使用这个类来初始化NSOperation的时候是用一个或者多个block的形式,NSOperation它本身就含有不止一个的block,当所有的block被执行完后,NSOperation就被认为结束了。
2、NSInvocationOperation –是初始化NSOperation用调用一个指定对象的一个方法来实现的。
NSOperation的优势何在?
1、支持依赖
![](https://oscdn.geek-share.com/Uploads/Images/Content/202009/25/ca5df9c02fc34d53442f1158e3efc64a)
2、改变执行的优先级
3、对于任何给定的队列,你可以取消操作,当任务被加到队列中后,你可以调用NSOperation实例对象的cancel()方法来取消这个任务。但取消操作有如下三种情形可能:
任务已经执行完,取消则无效果。
任务正在被执行,如果那样,系统不会强制停止任务,但取消的属性(cancelled)将被置为真。
任务在队列等待执行,那么它将被取消。
4、NSOperation有三个有用的布尔值属性finished, cancelled, and ready。finished
将被置为真当任务完成的时候,cancelled将为真当任务被取消的时候,ready将被置为真,当其可以被执行的时候。
5、任何的NSOperation都有一个选项设置完成后的block,当任务被完成后调用,也就是当finished属性被赋值为真之后,block开始执行。
接下来我们重写上面的demo用NSOperationQueues,首先声明变量在ViewController中
varqueue=NSOperationQueue()
然后重写这个方法didClickOnStart
上面的代码,我们用方法addOperationWithBlock及给定的block来创建一个新的任务。它很简单,为了执行这个任务,(在GCD的用法时是用这个方法dispatch_async())。我们可以这么做NSOperationQueue
(NSOperationQueue.mainQueue()) ,并且提交到主队列。我们可以正确的运行上面的代码,并且是后台执行下载图片(也不会阻塞UI)。
上面用了addOperationWithBlock这个方法,接下来我们加入了回调block重新处理下
为了验证取消操作,我们在导航栏上加了取消按钮。假设我们有四个任务,#3依赖#2,#2依赖任务#1,#4没有依赖。
下面是取消部分代码
这个需要关联取消按钮的,并且在这个方法didClickOnStart中加入如下两行代码:
然后改变回调方法,加上日志
其他几个类似这样的。
点开始按钮之后,再迅速按取消,结果如下:
#1已经开始了,所以取消没有任何结果,但#2和#3都依赖于#1,所以#2和#3都被取消了,但#4没有任何关联,则和#1一起执行了。效果图如下:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202009/25/9f91b6f30b0d404b8c77ebe19293ceac)
水平有限,有机会再回来修改。
第一部分:GCD
GCD是管理多线程最常用的api,GCD提供并管理任务队列。当然首先要了解队列是什么东西。
队列是管理对象的先进先出(FIFO)的一种数据结构,就像排队买票,排在前面的先买,后面的后买一样。
队列分为两种队列,一种就是分发队列(Dispatch queues)一种是顺序队列(Serial queues)
分发队列是以一种比较简单的方式在应用中来执行的异步并发任务,分发队列的任务是以block的形式提交的,无论是分发队列还是顺序队列,都是在主线程提交任务,其他线程来执行的队列。分发队列是以平行的并发顺序执行任务的。顺序队列是有先后顺序的(避免资源被竞争)。
在iOS中系统提供给每个应用一个顺序的队列和四个并发队列。其中主线程(顺序队列)来执行UI操作,所以如果大量的任务都放到主线程,那么主线程就会被阻塞掉。除了主线程队列,系统还提供了四个并发队列,我们称之为全局分发队列。这些队列是全局的队列,他们仅仅是优先级的区别。为了使用这些队列,你需要使用这个functiondispatch_get_global_queue函数,并设置第一个参数(参数如下)来得到这个队列的引用。
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND
当然,他们的优先级从上到下越来越低的是。你可以使用任意一个你想用的队列,但是请注意,这些队列是系统提供给你的,所以队列里面的任务不一定都是你的任务(可能有其他的任务)。
GCD快速预览
了解了队列的基础知识之后,我们来看看什么是GCD
接下来我们来个demo。
Demo Project
我们的项目比较简单,仅仅展示四幅图片,每张图片都根据url从网上请求,图片请求在主线程中,为了向你展示这个会影响UI的响应,我们在图片下面加了个组件slider。demo 在这里下载。点击开始下载图片的start按钮,同时拖拽下面的slider。你会发现,你根本拖动不了它。
一旦你点击开始按钮,图片就开始下载了在主线程中,很显然,这个方法很糟糕,它使得UI无法响应其他任何事件,不幸的是,至今还有一些应用依然在主线程内执行大量的任务。那么现在我们开始定位这个问题,并用并发队列来解决它。
我们首先用并发队列来实现它,然后再用顺序队列来实现它。(两种方法而已)
运用并发队列
首先打开 ViewController.swift这个文件在Xcode中,看下代码,你就会找到这个方法didClickOnStart,它就是下载图片的方法。
<span style="font-size:14px;">@IBActionfuncdidClickOnStart(sender:AnyObject){ letimg1=Downloader.downloadImageWithURL(imageURLs[0]) self.imageView1.image=img1 letimg2=Downloader.downloadImageWithURL(imageURLs[1]) self.imageView2.image=img2 letimg3=Downloader.downloadImageWithURL(imageURLs[2]) self.imageView3.image=img3 letimg4=Downloader.downloadImageWithURL(imageURLs[3]) self.imageView4.image=img4 }</span>
每个下载方法被看作是一个任务,并且所以任务都在主线程内顺序执行,那么现在我们要获取一个全局并发队列的一个引用(默认优先级的就可以了)。
<span style="font-size:14px;">letqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0) dispatch_async(queue){()->Voidin letimg1=Downloader.downloadImageWithURL(imageURLs[0]) dispatch_async(dispatch_get_main_queue(),{ self.imageView1.image=img1 }) }</span>
我们得到了一个默认的并发队列通过这个方法dispatch_get_global_queue,并且在内部的block内我们提交了一个下载图片的任务,一旦图片下载完成,我们提交另一个任务给主线程,让其更新下载好的图片,换句话来讲,就是我们把下载图片的方法放到了后台去做,但是执行UI更新还是在主线程内。其他部分也如此,修改后的代码就如下所示:
<span style="font-size:14px;">@IBActionfuncdidClickOnStart(sender:AnyObject){ letqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0) dispatch_async(queue){()->Voidin letimg1=Downloader.downloadImageWithURL(imageURLs[0]) dispatch_async(dispatch_get_main_queue(),{ self.imageView1.image=img1 }) } dispatch_async(queue){()->Voidin letimg2=Downloader.downloadImageWithURL(imageURLs[1]) dispatch_async(dispatch_get_main_queue(),{ self.imageView2.image=img2 }) } dispatch_async(queue){()->Voidin letimg3=Downloader.downloadImageWithURL(imageURLs[2]) dispatch_async(dispatch_get_main_queue(),{ self.imageView3.image=img3 }) } dispatch_async(queue){()->Voidin letimg4=Downloader.downloadImageWithURL(imageURLs[3]) dispatch_async(dispatch_get_main_queue(),{ self.imageView4.image=img4 }) } }</span>
你提交了四个图片的下载任务(并发的)到默认的队列,重新运行应用时你会发现速度快了很多,并且你也可以拖动那个slider了。
运用顺序队列
另一个可选的方法是使用顺序队列来处理这个问题,同样修改这个ViewController.swift文件的didClickOnStart()方法,这次我们用顺序队列来下载图片。当使用顺序队列的时候,需要注意的是,每个应用有个默认的顺序队列,那就是主队列,用来处理UI的。所以记住,使用顺序队列,你必须自己新建顺序队列来使用,否则你还是在主队列里面操作。并且会导致你要在主队列执行任务,同时试着去更新UI
,可能会报错。你可以使用函数dispatch_queue_create来创建新的队列并且提交任务到新的队列里面去,修改之后,我们的代码就变成如下的样子:
<span style="font-size:14px;">@IBActionfuncdidClickOnStart(sender:AnyObject){ letserialQueue=dispatch_queue_create("com.appcoda.imagesQueue",DISPATCH_QUEUE_SERIAL) dispatch_async(serialQueue){()->Voidin letimg1=Downloader.downloadImageWithURL(imageURLs[0]) dispatch_async(dispatch_get_main_queue(),{ self.imageView1.image=img1 }) } dispatch_async(serialQueue){()->Voidin letimg2=Downloader.downloadImageWithURL(imageURLs[1]) dispatch_async(dispatch_get_main_queue(),{ self.imageView2.image=img2 }) } dispatch_async(serialQueue){()->Voidin letimg3=Downloader.downloadImageWithURL(imageURLs[2]) dispatch_async(dispatch_get_main_queue(),{ self.imageView3.image=img3 }) } dispatch_async(serialQueue){()->Voidin letimg4=Downloader.downloadImageWithURL(imageURLs[3]) dispatch_async(dispatch_get_main_queue(),{ self.imageView4.image=img4 }) } }</span>
以上的两种方法的不同之处就是顺序队列需要自己去创建。但是以下两点我们仍须注意:
1、顺序队列比并发队列花费的时间长,原因就是顺序队列每次仅下载一张图,任务被顺序的执行。
2、图片被下载顺序是 image1, image2, image3, and image4 ,因为顺序队列每次仅执行一个任务。
第二部分:Operation Queues
GCD是一个底层的C语言API,它允许开发者并发执行任务。而Operation queues是一个高级的抽象的队列模型,并且在GCD的顶端。那就是说你可以执行并发任务像GCD一样,但是是以面向对象的方式。简单的说,Operation
queues就是让开发者的生活更简单了,方法更容易使用了。
它不像GCD一样先进先出的顺序,下面就是它与并发队列的不同之处。
1、不遵循先进先出的队列形式,在Operation queues中,你可以设置操作优先级并且可以设置任务之间的依赖(就是某个任务要在另一个任务执行之后才开始执行的这样的依赖),这就是为什么它不遵循先进先出的原则。
2、默认的也是并发执行的,你不能改变它(不能变成顺序队列),但你可以设置依赖关系。
3、Operation queue是这个类NSOperationQueue的一个实例。它的任务被封装在NSOperation实例中。
NSOperation
任务是以NSOperation的实例的形式被提交到operation queues中的,我们上面讨论的GCD的任务是以block的形式提交的。这里也是一样的道理,我们是以NSOperation的实例作为一个基础单元。NSOperation是一个抽象的类,不能被直接实例化,在iOS中我们提供了两个子类来实例化,这两个类可以直接使用,但并不拘于此,你也可以自己来继承NSOperation它并实例化它。两个子类如下:1、NSBlockOperation –使用这个类来初始化NSOperation的时候是用一个或者多个block的形式,NSOperation它本身就含有不止一个的block,当所有的block被执行完后,NSOperation就被认为结束了。
2、NSInvocationOperation –是初始化NSOperation用调用一个指定对象的一个方法来实现的。
NSOperation的优势何在?
1、支持依赖
2、改变执行的优先级
<span style="font-size:14px;">publicenumNSOperationQueuePriority: Int{ caseVeryLow caseLow caseNormal caseHigh caseVeryHigh }</span>
3、对于任何给定的队列,你可以取消操作,当任务被加到队列中后,你可以调用NSOperation实例对象的cancel()方法来取消这个任务。但取消操作有如下三种情形可能:
任务已经执行完,取消则无效果。
任务正在被执行,如果那样,系统不会强制停止任务,但取消的属性(cancelled)将被置为真。
任务在队列等待执行,那么它将被取消。
4、NSOperation有三个有用的布尔值属性finished, cancelled, and ready。finished
将被置为真当任务完成的时候,cancelled将为真当任务被取消的时候,ready将被置为真,当其可以被执行的时候。
5、任何的NSOperation都有一个选项设置完成后的block,当任务被完成后调用,也就是当finished属性被赋值为真之后,block开始执行。
接下来我们重写上面的demo用NSOperationQueues,首先声明变量在ViewController中
varqueue=NSOperationQueue()
然后重写这个方法didClickOnStart
<span style="font-size:14px;">@IBActionfuncdidClickOnStart(sender:AnyObject){ queue=NSOperationQueue() queue.addOperationWithBlock{()->Voidin letimg1=Downloader.downloadImageWithURL(imageURLs[0]) NSOperationQueue.mainQueue().addOperationWithBlock({ self.imageView1.image=img1 }) } queue.addOperationWithBlock{()->Voidin letimg2=Downloader.downloadImageWithURL(imageURLs[1]) NSOperationQueue.mainQueue().addOperationWithBlock({ self.imageView2.image=img2 }) } queue.addOperationWithBlock{()->Voidin letimg3=Downloader.downloadImageWithURL(imageURLs[2]) NSOperationQueue.mainQueue().addOperationWithBlock({ self.imageView3.image=img3 }) } queue.addOperationWithBlock{()->Voidin letimg4=Downloader.downloadImageWithURL(imageURLs[3]) NSOperationQueue.mainQueue().addOperationWithBlock({ self.imageView4.image=img4 }) } }</span>
上面的代码,我们用方法addOperationWithBlock及给定的block来创建一个新的任务。它很简单,为了执行这个任务,(在GCD的用法时是用这个方法dispatch_async())。我们可以这么做NSOperationQueue
(NSOperationQueue.mainQueue()) ,并且提交到主队列。我们可以正确的运行上面的代码,并且是后台执行下载图片(也不会阻塞UI)。
上面用了addOperationWithBlock这个方法,接下来我们加入了回调block重新处理下
<span style="font-size:14px;">@IBAction func didClickOnStart(sender: AnyObject) { queue = NSOperationQueue() let operation1 = NSBlockOperation(block: { let img1 = Downloader.downloadImageWithURL(imageURLs[0]) NSOperationQueue.mainQueue().addOperationWithBlock({ self.imageView1.image = img1 }) }) operation1.completionBlock = { print("Operation 1 completed") } queue.addOperation(operation1) let operation2 = NSBlockOperation(block: { let img2 = Downloader.downloadImageWithURL(imageURLs[1]) NSOperationQueue.mainQueue().addOperationWithBlock({ self.imageView2.image = img2 }) }) operation2.completionBlock = { print("Operation 2 completed") } queue.addOperation(operation2) let operation3 = NSBlockOperation(block: { let img3 = Downloader.downloadImageWithURL(imageURLs[2]) NSOperationQueue.mainQueue().addOperationWithBlock({ self.imageView3.image = img3 }) }) operation3.completionBlock = { print("Operation 3 completed") } queue.addOperation(operation3) let operation4 = NSBlockOperation(block: { let img4 = Downloader.downloadImageWithURL(imageURLs[3]) NSOperationQueue.mainQueue().addOperationWithBlock({ self.imageView4.image = img4 }) }) operation4.completionBlock = { print("Operation 4 completed") } queue.addOperation(operation4) }</span>取消的操作
为了验证取消操作,我们在导航栏上加了取消按钮。假设我们有四个任务,#3依赖#2,#2依赖任务#1,#4没有依赖。
下面是取消部分代码
<span style="font-size:14px;">@IBAction func didClickOnCancel(sender: AnyObject) { self.queue.cancelAllOperations() }</span>
这个需要关联取消按钮的,并且在这个方法didClickOnStart中加入如下两行代码:
<span style="font-size:14px;">operation2.addDependency(operation1) operation3.addDependency(operation2)</span>
然后改变回调方法,加上日志
<span style="font-size:14px;">operation1.completionBlock = { print("Operation 1 completed, cancelled:\(operation1.cancelled) ") }</span>
其他几个类似这样的。
点开始按钮之后,再迅速按取消,结果如下:
#1已经开始了,所以取消没有任何结果,但#2和#3都依赖于#1,所以#2和#3都被取消了,但#4没有任何关联,则和#1一起执行了。效果图如下:
水平有限,有机会再回来修改。
相关文章推荐
- Python3写爬虫(四)多线程实现数据爬取
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 峰回路转,Firefox 浏览器即将重返 iOS 平台
- 不可修补的 iOS 漏洞可能导致 iPhone 4s 到 iPhone X 永久越狱
- iOS 12.4 系统遭黑客破解,漏洞危及数百万用户
- 每日安全资讯:NSO,一家专业入侵 iPhone 的神秘公司
- [转][源代码]Comex公布JailbreakMe 3.0源代码
- C#实现多线程的同步方法实例分析
- ruby实现的一个异步文件下载HttpServer实例
- C#异步绑定数据实现方法
- 浅谈chuck-lua中的多线程
- 科学知识:同步、异步、阻塞和非阻塞区别
- 探讨Ajax中同步与异步之间的区别
- C#简单多线程同步和优先权用法实例
- C#多线程学习之(四)使用线程池进行多线程的自动管理
- C#多线程编程中的锁系统(三)
- C#多线程学习之(六)互斥对象用法实例
- C#中异步回调函数用法实例
- 基于一个应用程序多线程误用的分析详解