引言
RunLoop在大家的印象中就是很神秘的感觉,很多人都不愿去触碰它。其实对于其在项目中的应用还是非常实际的和方便的。在我们项目中也有关于其的应用实例。今天,我们一起探索关于RunLoop的那些事吧!
基本作用
- 保持程序的持续运行(比如主运行循环)。
- 处理App中的各种事件响应(比如触摸事件、定时器事件等)。
- 节省CPU资源,提高程序性能(也就是说该做事时做事,该休息时休息)。
在程序中存在价值和意义
图一没有RunLoop.png 图二有RunLoop.png就拿程序内部的main函数为例:
main函数的作用和其中实现的RunLoop(主循环)
- main函数是程序的入口
- 保持程序的持续运行
- 图一中Return返回的为具体数值,有返回值,程序运行到Return就结束了,这种情况表现为我们的程序启动不起来
- 图二中Return一直没有返回值。内部实现是通过UIApplicationMain函数内部就启动了一个RunLoop。所以UIApplicationMain函数一直没有返回,保持了程序的持续运行
- 图二这个默认启动的RunLoop是跟主线程相关联的
RunLoop 的对象
对于RunLoop其有两套API:
- 基于C语言的CoreFoundation框架的CFRunLoopRef对象
- 基于CFRunLoopRef的一层OC包装的Foundation的NSRunLoop对象
RunLoop与线程
- 每条线程都有唯一的一个与之对应的RunLoop对象
- RunLoop在第一次获取时创建,在线程结束时将会被销毁
- 主线程的RunLoop是已经自动创建好的,子线程的RunLoop需要主动创建
RunLoop对象的获取
基于Foundation框架的:
- [NSRunLoop currentRunLoop] //获取当前线程的RunLoop的对象
- [NSRunLoop mainRunLoop] //获取主线程RunLoop的对象
基于CoreFoundation框架的:
- CFRunLoopGetCurrent() //获取当前线程的RunLoop的对象
- CFRunLoopGetMain() //获取主线程RunLoop的对象
Core Foundation中关于RunLoop相关的类
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
CFRunLoopModeRef
相关类
- 一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer,即是一个RunLoop至少有一个向对应的Mode
- 每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
系统默认注册了5个Mode:(前三个比较常用)
- kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode
- UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
CFRunLoopSourceRef
CFRunLoopSourceRef是事件源(输入源)
按照官方文档的分类
- Port-Based Sources (基于端口,跟其他线程交互,通过内核发布的消息)
- Custom Input Sources (自定义)
- Cocoa Perform Selector Sources (performSelector...方法)
按照函数调用栈的分类Source有两个版本:Source0 和 Source1。
- Source0:非基于Port的
- Source1:基于Port的
注:Source0: event事件,只含有回调,需要先调用CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop。
Source1: 包含了一个 mach_port 和一个回调,被用于通过内核和其他线程相互发送消息,能主动唤醒 RunLoop 的线程。
CFRunLoopTimerRef
- CFRunLoopTimerRef是基于时间的触发器
- 基本上说的就是NSTimer(CADisplayLink也是加到RunLoop),它受RunLoop的Mode影响
注释:是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
CFRunLoopObserverRef
CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:
观察者可以监听的状态
使用方法调用:
- (void)observer
{
// 创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
});
// 添加观察者:监听RunLoop的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 释放Observer
CFRelease(observer);
}
*注:CF的内存管理(Core Foundation)
1.凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release 。比如CFRunLoopObserverCreate
2.release函数:CFRelease(对象);
RunLoop的应用实例
- 场景还原
1.NSTimer(最常见RunLoop使用)
- (void)timer
{
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 定时器只运行在NSDefaultRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 定时器只运行在UITrackingRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// 定时器会跑在标记为common modes的模式下
// 标记为common modes的模式:UITrackingRunLoopMode和NSDefaultRunLoopMode兼容
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void)timer2
{
// 调用了scheduledTimer返回的定时器,已经自动被添加到当前runLoop中,而且是NSDefaultRunLoopMode
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 修改模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
2.关于轮播图的拖拽Mode的变换导致的问题
由于拖拽时模式由NSDefaultRunLoopMode 进入 UITrackingRunLoopMode模式发生了变化。所以我们设置模式的时候设为NSRunLoopCommonModes 模式下两种模式都可运行
3.PerformSelector
- -(void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
创建一个线程在子线程执行,aSelector代表了新创建的线程,arg是传入的参数
- -(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
该方法的作用是在主线程中,执行制定的方法(代码块)。
参数:
@selector就是,要定义我们要执行的方法。
withObject:arg定义了,我们执行方法时,传入的参数对象。类型是id。(我们可以传入任何参数)waitUntilDone:YES指定,当前线程是否要被阻塞,直到主线程将我们制定的代码块执行完。
注意:
- 当前线程为主线程的时候,waitUntilDone:YES参数无效。
- 该方法,没有返回值
- 该方法主要用来用主线程来修改页面UI的状态。
3.常驻线程
应用场景:经常在后台进行耗时操作,如:监控联网状态,扫描沙盒等 不希望线程处理完事件就销毁,保持常驻状态
- (void)run
{
//addPort:添加端口(就是source) forMode:设置模式
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
//启动RunLoop
[[NSRunLoop currentRunLoop] run];
/*
//另外两种启动方式
[NSDate distantFuture]:遥远的未来 这种写法跟上面的run是一个意思
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
不设置模式
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
*/
}
退出-退出当前线程:
[NSThread exit];
结论:虽然RunLoop很难接触到,但是项目中也是经常出现的,关于NSTimer定时器的问题,我们每次还都是与其打交道的。
注:RunLoop相关资料
苹果官方文档
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
CFRunLoopRef是开源的
http://opensource.apple.com/source/CF/CF-1151.16/
网友评论