2017年07月12日20:48:41又学习
一基本作用
1.保持程序的持续执行
2.处理 app 中的各种事件(触摸、定时器时间、Selector事件)
3.节省 CPU 资源 , 提高程序性能, 该做事的时候做事, 该休息的时候休息
二什么是 runloop
1.运行循环
2.其实它内部就是 do-while 循环, 在这个循环内部不断处理各种任务(比如说 source, Timer 和 Observer)
3.一个线程对应一个 runloop, 主线程的 Runloop默认是开启的, 子线程的 Runloop 必须手动开启, 调用 run 方法
4.Runloop 只能选择一个 Model 启动, 如果当前的 model 中没有任何的 source, Timer 和 Observer,那么直接退出 runloop
三自动释放池什么时候释放?
在 runloop 睡眠之前(KCFRunloopBeforeWaiting)
四在开发中如何使用 Runloop? 什么应用场景?
1.开启一个常驻线程(让子线程不进入消亡状态, 等待其他线程发来消息, 处理其他事件)
1.1在子线程中开启一个定时器,
1.2在子线程中进行一些长期监控
2.可以控制定时器在特定的 Mode 下执行
3.可以让某些事件(行为和任务)在特定的 model 下执行
4.可以添加 Observer 监听 Runloop 的状态, 比如监听点击事件的处理(在所有点击事件之前做的一件事情)
0. runLoop的概念
- 1.运行在一个Thread上的一个do-while 死循环,保证程序持续运行
- 2.这个循环专门用来接收事件源,通知 绑定的线程 去执行这个事件.
- 3.主线程默认开启事件接收循环,并自动创建 自动释放池
- 4.一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,
- 5.主线程默认开启,自己手动创建的子线程的RunLoop默认没有开启,需要我们自己开启
这种模型通常被称作 Event Loop。 Event Loop 在很多系统和框架里都有实现,所以,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。
OSX/iOS 系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。
- CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
- NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。CFRunLoopRef 的代码是开源的,你可以在这里 http://opensource.apple.com/tarballs/CF/ 下载到整个 CoreFoundation 的源码来查看。
1. RunLoop基本作用
- 保持程序的持续运行
- 处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
2. RunLoop与线程
- 每条线程都有唯一的一个与之对应的RunLoop对象
- 主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
- RunLoop在第一次获取时创建,在线程结束时销毁.
线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。
线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。
3.获得RunLoop对象
#######1.Foundation oc的NSRunloop
[NSRunLoop currentRunLoop]; // 创建当前子线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
默认不允许创建runloop,提供了两个获取的方法
#######2.Core Foundation C的runloop
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
- NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)
4. RunLoop相关类
Core Foundation中关于RunLoop的5个类
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
runLoop.png
5. RunLoop的运行模式
一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer
每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
系统默认注册了5个Mode:
- kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
- kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode
6. CFRunLoopTimerRef是基于时间的触发器
CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
- 1.基本上说的就是NSTimer,它会受到runloop的mode的影响
NSTimer *timer = [NSTimer timerWithTimeInterval:5 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
- 2.GCD的定时器不受Runloop的mode的影响
7. CFRunLoopSourceRef是事件源(输入源)
Source0:非基于Port的,用于用户主动触发的事件
Source1:基于Port的,通过内核和其它线程相互发送消息
8. CFRunLoopObserverRef
在runloop中每一个runLoop对象同一个时间内, 对应一个mode, 当选中的mode中没有timer或者source时 runloop就会被销毁.CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变
8.1 监听的时间点
可以监听的时间点有以下几个
可以监听的时间点有以下几个
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 1 kCFRunLoopBeforeTimers = (1UL << 1), // 2 kCFRunLoopBeforeSources = (1UL << 2), // 4 kCFRunLoopBeforeWaiting = (1UL << 5), // 32 kCFRunLoopAfterWaiting = (1UL << 6), // 64 kCFRunLoopExit = (1UL << 7), // 128 kCFRunLoopAllActivities = 0x0FFFFFFFU};
上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。
8.2 Observer的使用
添加Observer添加观察者:监听RunLoop的状态
释放Observer
// 创建observerCFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);});// 添加观察者:监听RunLoop的状态CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);// 释放ObserverCFRelease(observer);
9. RunLoop 的内部逻辑
runLoop时间队列.png
runloop.png
可以看到,实际上 RunLoop 就是这样一个函数,其内部是一个 do-while 循环。当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。
10. RunLoop应用
10.1 应用 NSTimer:
鉴于我们的NSTimer默认工作在Default模式下, 当我们tableView滚动的时候, 会切换到tracking模式下,这样定时器不在工作, 直到回到default模式下, 会恢复工作,这样造成定时器停掉, 我们可以将定时器放在common这个标记下, common默认标记default和tracking模式,这样就可以正常工作了, 但是NStimer定时器也不是很准确, 推荐使用GCD的Timer
10.2 ImageView显示: PerformSelector
我们可以在设置下载图片的任务放在default模型下,但我们的tableView滚动的时候, 就不在下载数据, 这样的话, 就可以保证tableView的流畅性.
那么该performSelector事件,只会当主线程RunLoop处于default时,才会被执行。如果不这么做,可能造成当TableView滚动时,又去下载图片可能造成卡顿。(不过其实一般的下载操作,但是放在其他子线程完成的,这里只是举个简单的例子).
10.3 常驻线程
应用场景:经常在后台进行耗时操作,如:监控联网状态,扫描沙盒等 不希望线程处理完事件就销毁,保持常驻状态
第一种(推荐)
- 开启
- 退出
第二种(奇葩法)
优点:退出RunLoop比较方便-定义个标记 while(flag){...}
1 创建一个单例NSThread对象,并start
2 NSThread线程入口函数中完成:创建@autoreleasepool{ … }然后开启Thread的RunLoop
添加一个NSMachPort给Thread对象来防止runloop执行完毕之后会退出注意,不要讲NSMachPort对象发送给其他子线程我看到有一种使用while(1){ 开启RunLoop }这样的结构来防止runloop退出,虽然是可以,个人觉得太粗糙3 然后给单例NSThread的runloop上注册 Timer/Sources/Observer
4 完成回调业务处理
使用port或是自定义的input source来和其他线程进行通信 (AFN就是例子)
10.3.1 AFN在NSURLConnection中的使用
AFN在NSURLConnection中的使用. 其希望能在后台线程接收 Delegate 回调。为此 AFNetworking 单独创建了一个线程,并在这个线程中启动了一个 RunLoop:
RunLoop 启动前内部必须要有至少一个Timer/Observer/Source,所以 AFNetworking 在 [runLoop run] 之前先创建了一个新的 NSMachPort 添加进去了。通常情况下,调用者需要持有这个 NSMachPort (mach_port) 并在外部线程通过这个 port 发送消息到 loop 内;但此处添加 port 只是为了让 RunLoop 不至于退出,并没有用于实际的发送消息。
当需要这个后台线程执行任务时,AFNetworking 通过调用 [NSObject performSelector:onThread:..] 将这个任务扔到了后台线程的 RunLoop 中。
10.3.2 AFN在NSURLSession中的使用
AFN使用RunLoop的实例.png
Snip20160719_6.png
- 自动释放池: AutoreleasePool
在主线程即将休眠时,释放自动释放池在主线程即将唤醒时,再次创建自动释放池,并将之前的对象再次放入池中
kCFRunLoopEntry >>> 创建自动释放池kCFRunLoopBeforeWaiting >>> 重建自动释放池kCFRunLoopExit >>> 销毁自动释放池
特别注意:
在启动RunLoop之前建议用 @autoreleasepool {...}包裹
意义:创建一个大释放池,释放{}期间创建的临时对象,一般好的框架的作者都会这么做
题外话:
以后为了增加用户体验 在用户UI交互的时候 不做事件处理 我们可以把需要做的操作放到NSDefaultRunLoopMode
补充:GCD定时器
一般的NSTimer定时器因为受到RunLoop,会存在时间不准时的情况.
上文有提到GCD不受RunLoop影响,下面简单的说一下它的使用
11. RunLoop面试题
11.1 什么是RunLoop?
- 从字面意思看:运行循环、跑圈.其实它内部就是do-while循环,在这个循环内部不断地处理各种任务(比如Source、Timer、Observer
) - 一个线程对应一个RunLoop,主线程的RunLoop默认已经启动,子线程的RunLoop得手动启动(调用run方法)
- RunLoop只能选择一个Mode启动,如果当前Mode中没有任何Source(Sources0、Sources1)、Timer,那么就直接退出RunLoop
11.2 自动释放池什么时候释放?
通过Observer监听RunLoop的状态在主线程即将休眠时,释放自动释放池在主线程即将唤醒时,再次创建自动释放池,并将之前的对象再次放入池中
11.3 在开发中如何使用RunLoop?什么应用场景?
- 1.开启一个常驻线程(让一个子线程不进入消亡状态,等待其他线程发来消息,处理其他事件)
1.在子线程中开启一个定时器
2.在子线程中进行一些长期监控
- 2.可以控制定时器在特定模式下执行
- 3.可以让某些事件(行为、任务)在特定模式下行
- 4.可以添加Observer监听RunLoop的状态,比如监听点击事件的处理(在所有点击事件之前做一些事情)
11.4 NSRunLoop的实现机制,及在多线程中如何使用
NSRunLoop是IOS消息机制的处理模式
1.NSRunLoop的主要作用:控制NSRunLoop里面线程的执行和休眠,在有事情做的时候使当前NSRunLoop控制的线程工作,没有事情做让当前NSRunLoop的控制的线程休眠。
2.NSRunLoop 就是一直在循环检测,从线程start到线程end,检测inputsource(如点击,双击等操作)异步事件,检测timesource同步事件,检测到输入源会执行处理函数,首先会产生通知,corefunction向线程添加runloop observers来监听事件,意在监听事件发生时来做处理。
3.runloopmode是一个集合,包括监听:事件源,定时器,以及需通知的runloop observers
只有在为你的程序创建次线程的时候,才需要运行runloop。对于程序的主线程而言,run loop是关键部分。Cocoa提供了运行主线程run loop的代码同时也会自动运行run loop。IOS程序UIApplication中的run方法在程序正常启动的时候就会启动run loop。如果你使用xcode提供的模板创建的程序,那你永远不需要自己去启动run loop
在多线程中,你需要判断是否需要run loop。如果需要run loop,那么你要负责配置run loop并启动。你不需要在任何情况下都去启动run loop。比如,你使用线程去处理一个预先定义好的耗时极长的任务时,你就可以毋需启动run loop。Run loop只在你要和线程有交互时才需要
文@greedyDoor/greedyDoor(作者)原文链接:http://www.jianshu.com/p/8240f8f1525f著作权归作者所有,转载请联系作者获得授权,并标注“作者”。