RunLoop详解
什么是RunLoop?
从字面意思看叫做运行循环,俗称“跑圈”。
RunLoop的主要作用:
1、保持程序的持续运行
2、处理APP中的各种事件(触摸事件,定时器事件,selector事件)
3、节省CPU资源,提高程序性能:有事做事,没事休息
main函数中的RunLoop:
如果没有RunLoop:

没有RunLoop,程序执行第三行后就结束了;
如果有RunLoop:

RunLoop内部其实就是一个do-while循环,在这个循环内部不断的处理各种事件,程序不会执行第7行代码,因此保证了程序不会退出一直运行;

UIApplicationMain函数内部自动启动了一个RunLoop,所以UIApplicationMain一直没有返回,保持程序持续运行。这个默认启动的RunLoop是跟主线程相关联的;
RunLoop对象:
iOS中有2套API访问和使用RunLoop:
Foundation框架下的:NSRunLoop
CoreFoundation框架下的:CFRunLoopRef;
CFRunLoopRef和NSRunLoop都代表RunLoop对象;NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层的API;
可参考苹果官方文档:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
苹果开源文档:
http://opensource.apple.com/source/CF/CF-1151.16/
RunLoop与线程
每条线程都有唯一一个与之对应的RunLoop对象;
主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动去创建;
RunLoop在第一次获取时创建,线程销毁时销毁;
获取RunLoop对象
Foundation:
[NSRunLoop currentRunLoop];// 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
Core Foundation:
CFRunLoopGetCurrent();// 获得当前线程的RunLoop对象
CFRunLoopGetMain();// 获得主线程的RunLoop对象
RunLoop相关类
CoreFoundation中关于RunLoop的5个类:
CFRunLoopRef //RunLoop对象
CFRunLoopModeRef //RunLoop运行模式
CFRunLoopSourceRef //RunLoop事件源
CFRunLoopTimerRef //RunLoop定时器事件
CFRunLoopObserverRef //监听RunLoop状态的观察者
CFRunLoopModeRef(模式)

一个RunLoop包含若干个Mode,每个Mode又包含若干个source、timer、observer;
每次RunLoop启动时,只能指定其中一个Mode,这个Mode称作CurrentMode;
如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入;这样做是为了分隔开不同组的source、timer、observer,互不影响;具体怎么退出,怎么重新指定Mode,官方文档并未讲述,我们也不得而知;
系统默认注册了5个Mode:
KCFRunLoopDefaultMode:APP的默认Mode,通常主线程在这个Mode下运行;
UITrackingRunLoopMode:界面跟踪Mode,用于scrollView追踪触摸滑动,保证界面滑动不受其他Mode影响;
UIInitializationRunLoopMode:在刚启动APP时进入的第一个Mode,启动完成后就不再使用;
GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到;
KCFRunloopCommonModes:这是一个占位用的Mode,不是真正的Mode;表示具有KCFRunloopCommonModes标记的模式;
KCFRunloopCommonModes标记的Mode有两种:
0 : <CFString 0x109af3a40 [0x108c687b0]>{contents = "UITrackingRunLoopMode"}
2 : <CFString 0x108c88b40 [0x108c687b0]>{contents = "kCFRunLoopDefaultMode"}
因此当一个事件是在RunLoop的KCFRunloopCommonModes模式下,RunLoop在KCFRunLoopDefaultMode和UITrackingRunLoopMode模式时都能处理该事件;
CFRunLoopSourceRef(事件源)
事件源的分类
从前:
1、Port-Based Sources
2、Custom Input Sources
3、Cocoa Perform Selector Sources
现在:
Source0:非基于Port的,用于用户主动触发的事件
Source1:基于Port的,通过内核和其他线程相互发送消息
CFRunLoopTimerRef(基于时间的触发器)
NSTimer,会受到RunLoop的Mode影响
GCD的定时器不受RunLoop的Mode影响
//1.创建NSTimer
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
//2.添加到runloop,当runloop的运行模式为NSDefaultRunLoopMode时候,定时器工作
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
那么timer只在NSDefaultRunLoopMode下工作;同理如果选择UITrackingRunLoopMode,timer只在UITrackingRunLoopMode下工作;选择NSRunLoopCommonModes,timer在UITrackingRunLoopMode和NSDefaultRunLoopMode下都能正常工作;
//0.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//创建一个GCD定时器
/*
第一个参数:表明创建的是一个定时器
第四个参数:队列
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
self.timer = timer;
//2.设置定时器的开始时间,间隔时间,精准度
/*
第1个参数:要给哪个定时器设置
第2个参数:开始时间
第3个参数:间隔时间
第4个参数:精准度 一般为0 提高程序的性能
GCD的单位是纳秒
*/
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 10 * NSEC_PER_SEC);
//3.设置定时器要调用的方法
dispatch_source_set_event_handler(timer, ^{
NSLog(@"GCD-timer");
});
//4.启动
dispatch_resume(timer);
定时器对象需要声明属性或成员变量去接收,否则这段代码执行完,timer会被销毁;
CFRunLoopObserverRef(观察者)
监听的时间点:

//创建一个监听对象
/*
第一个参数:分配存储空间的
第二个参数:要监听的状态 kCFRunLoopAllActivities 所有状态
第三个参数:是否要持续监听
第四个参数:优先级
第五个参数:回调
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"runloop进入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"runloop要去处理timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"runloop要去处理Sources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"runloop要睡觉了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"runloop醒来啦");
break;
case kCFRunLoopExit:
NSLog(@"runloop退出");
break;
default:
break;
}
});
//给runloop添加监听者
/*
第一个参数:要监听哪个runloop
第二个参数:监听者
第三个参数:要监听runloop在哪种运行模式下的状态
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(runLoop) userInfo:nil repeats:YES];
CFRelease(observer);
以上内容是我学习过程中查询资料总结出来的,如有疏漏或错误,欢迎批评指正,也欢迎大家一起交流讨论;关于RunLoop的应用,后面有空再继续补充;