实例化讲解RunLoop
之前看过很多有关RunLoop的文章,其中要么是主要介绍RunLoop的基本概念,要么是主要讲解RunLoop的底层原理,很少用真正的实例来讲解RunLoop的,这其中有大部分原因是由于大家在项目中很少能用到RunLoop吧。基于这种原因,本文中将用很少的篇幅来对基础内容做以介绍,然后主要利用实例来加深大家对RunLoop的理解,本文中的代码已经上传GitHub,大家可以下载查看,有问题欢迎Issue我。本文主要分为如下几个部分:
- RunLoop的基础知识
- 初识RunLoop,如何让RunLoop进驻线程
- 深入理解Perform Selector
- 一直”活着”的后台线程
- 深入理解NSTimer
- 让两个后台线程有依赖性的一种方式
- NSURLConnetction的内部实现
- AFNetWorking中是如何使用RunLoop的?
- 其它:利用GCD实现定时器功能
- 延伸阅读
一、RunLoop的基本概念:
什么是RunLoop
?提到RunLoop,我们一般都会提到线程,这是为什么呢?先来看下官方对RunLoop
的定义:RunLoop
系统中和线程相关的基础架构的组成部分(和线程相关),一个RunLoop是一个事件处理环,系统利用这个事件处理环来安排事务,协调输入的各种事件。RunLoop
的目的是让你的线程在有工作的时候忙碌,没有工作的时候休眠(和线程相关)。可能这样说你还不是特别清楚RunLoop
究竟是用来做什么的,打个比方来说明:我们把线程比作一辆跑车,把这辆跑车的主人比作RunLoop
,那么在没有’主人’的时候,这个跑车的生命是直线型的,其启动,运行完之后就会废弃(没有人对其进行控制,’撞坏’被收回),当有了RunLoop
这个主人之后,‘线程’这辆跑车的生命就有了保障,这个时候,跑车的生命是环形的,并且在主人有比赛任务的时候就会被RunLoop
这个主人所唤醒,在没有任务的时候可以休眠(在IOS中,开启线程是很消耗性能的,开启主线程要消耗1M内存,开启一个后台线程需要消耗512k内存,我们应当在线程没有任务的时候休眠,来释放所占用的资源,以便CPU进行更加高效的工作),这样可以增加跑车的效率,也就是说RunLoop
是为线程所服务的。这个例子有点不是很贴切,线程和RunLoop之间是以键值对的形式一一对应的,其中key是thread,value是runLoop(这点可以从苹果公开的源码中看出来),其实RunLoop是管理线程的一种机制,这种机制不仅在IOS上有,在Node.js中的EventLoop,Android中的Looper,都有类似的模式。刚才所说的比赛任务就是唤醒跑车这个线程的一个source
;RunLoop Mode
就是,一系列输入的source
,timer
以及observer
,RunLoop Mode
包含以下几种: NSDefaultRunLoopMode
,NSEventTrackingRunLoopMode
,UIInitializationRunLoopMode
,NSRunLoopCommonModes
,NSConnectionReplyMode
,NSModalPanelRunLoopMode
,至于这些mode各自的含义,读者可自己查询,网上不乏这类资源;
二、初识RunLoop,如何让RunLoop进驻线程
我们在主线程中添加如下代码:
1 2 3 4 5 6 7 8 9 |
while (1) { NSLog(@"while begin"); // the thread be blocked here NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; // this will not be executed NSLog(@"while end"); } |
这个时候我们可以看到主线程在执行完[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
之后被阻塞而没有执行下面的NSLog(@"while end")
;同时,我们利用GCD,将这段代码放到一个后台线程中:
1 2 3 4 5 6 7 8 9 10 11 12 |
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ while (1) { NSLog(@"while begin"); NSRunLoop *subRunLoop = [NSRunLoop currentRunLoop]; [subRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; NSLog(@"while end"); } }); |
RunLoop
分别打印出来:
由于这个日志比较长,我就只截取了上面的一部分。
我们再看我们新建的子线程中的RunLoop
,打印出来之后:
从中可以看出来:我们新建的线程中:
1 2 3 4 |
sources0 = (null), sources1 = (null), observers = (null), timers = (null), |
我们看到虽然有Mode,但是我们没有给它soures,observer,timer
,其实Mode中的这些source,observer,timer
,统称为这个Mode
的item
,如果一个Mode
中一个item
都没有,则这个RunLoop会直接退出,不进入循环(其实线程之所以可以一直存在就是由于RunLoop将其带入了这个循环中)。下面我们为这个RunLoop添加个source:<br/>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ while (1) { NSPort *macPort = [NSPort port]; NSLog(@"while begin"); NSRunLoop *subRunLoop = [NSRunLoop currentRunLoop]; [subRunLoop addPort:macPort forMode:NSDefaultRunLoopMode]; [subRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; NSLog(@"while end"); NSLog(@"%@",subRunLoop); } }); |
这样我们可以看到能够实现了和主线程中相同的效果,线程在这个地方暂停了,为什么呢?我们明天让RunLoop在distantFuture
之前都一直run的啊?相信大家已经猜出出来了。这个时候线程被RunLoop
带到‘坑’里去了,这个‘坑’就是一个循环,在循环中这个线程可以在没有任务的时候休眠,在有任务的时候被唤醒;当然我们只用一个while(1)
也可以让这个线程一直存在,但是这个线程会一直在唤醒状态,及时它没有任务也一直处于运转状态,这对于CPU来说是非常不高效的。
小结:我们的RunLoop要想工作,必须要让它存在一个Item(source,observer或者timer),主线程之所以能够一直存在,并且随时准备被唤醒就是应为系统为其添加了很多Item
三、深入理解Perform Selector
我们先在主线程中使用下performselector
:<br/>
1 2 3 4 5 6 7 8 9 Ԩ很少的篇幅来对基础内容做以介绍,然后主要利用实例来加深大家对RunLoop的理解,本文中的代码已经上传GitHub,大家可以下载查看,有问题欢迎Issue我。本文主要分为如下几个部分:
一、RunLoop的基本概念:什么是 二、初识RunLoop,如何让RunLoop进驻线程我们在主线程中添加如下代码:
这个时候我们可以看到主线程在执行完
这个时候我们发现这个while循环会一直在执行;这是为什么呢?我们先将这两个
RunLoop 分别打印出来:
由于这个日志比较长,我就只截取了上面的一部分。
从中可以看出来:我们新建的线程中:
我们看到虽然有Mode,但是我们没有给它
这样我们可以看到能够实现了和主线程中相同的效果,线程在这个地方暂停了,为什么呢?我们明天让RunLoop在 三、深入理解Perform Selector我们先在主线程中使用下 |