先来思考一个问题:APP为什么能不间断的响应用户事件(包括触摸、滑动、按钮点击等事件)?
我们都知道,每一个线程在执行完任务就会正常退出。很显然,负责用户交互的主线程(UI线程)并不是这样的,就像上面的问题描述的那样,主线程会一直存在,不断的响应用户的交互。
问题的答案就是RunLoop,它本质是这样一种机制:能够让线程持续运行而不退出,在有事件来的时候响应事件,在没有事件的时候让线程处于休眠状态。所以,RunLoop是iOS系统中和线程相关的基础架构组成部分。每一个RunLoop对象都会依赖于一个具体的线程,主线程也不例外。虽然我们在日常开发的时候并没有接触到这个RunLoop,但它的确存在。下面,我们就用代码来证明一下主线程对应的RunLoop对象。
每一个的iOS程序的入口都是main函数,我们先看下main函数的代码:
int main(intargc,char* argv[]) {
@autoreleasepool{
return UIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegateclass]));
}
}
可以尝试把上面的代码改造一下
int main(intargc,char* argv[]) {
@autoreleasepool{
NSLog(@"开始进入main函数");
int a=UIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegateclass]));
NSLog(@"开始执行主线程");
return a;
}
}
运行后会发现,NSLog(@"开始执行主线程");这段代码根本就没有执行???
实际上,在UIApplicationMain函数内部,会为主线程设置一个RunLoop对象,让主线程处于无限循环等待接受事件的状态。
其他非主线程关联RunLoop对象,则需要开发者显示的去设置。接下来,我们来了解一下这个RunLoop对象。
OC当中有两套API来获取RunLoop,分别是Foundation框架中的NSRunLoop和Core Foundation框架中的CFRunLoopRef。NSRunLoop是基于CFRunLoopRef,且CFRunLoopRef是线程安全,这里重点讲解Core Foundation框架下RunLoop相关类。
1、CFRunLoopRef 代表一个RunLoop对象,获取RunLoop的方法为CFRunLoopGetCurrent(),CFRunLoopGetMain()。需要注意一点:RunLoop对象只在第一次获取的时候才会被创建。
2、CFRunLoopModeRef 代表RunLoop运行模式,一个RunLoop可以有多个Mode,并且可以在这些Mode之间切换。当一个RunLoop启动时,必须先指定一个Mode,这个当前模式叫做CurrentMode。系统中默认定义了几个Mode:kCFRunLoopDefaultMode(默认模式)、UITrackingRunLoopMode(滑动模式,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响),UIInitializationRunLoopMode(应用启动时进入的第一个模式,之后不再使用),GSEventReceiveRunLoopMode(接受系统事件模式),kCFRunLoopCommonModes(伪模式,不是一种真正的模式)
3、CFRunLoopSourceRef 代表RunLoop输入源,按照函数调用栈来分类
Source0 :非基于Port的
Source1:基于port的,通过内核和其他线程通信,接收、分发事件。
4、CFRunLoopTimerRef 代表定时器源,基于事件的触发器。这里有必要说一下NSTimer。NSTimer放在主线程中,不需要指定RunLoop。放在其他线程中,需要指定RunLoop。并且主线程中的NSTimer是运行在默认模式下(kCFRunLoopDefaultMode),一旦出现滑动事件,NSTimer就会停止工作。使用kCFRunLoopCommonModes 模式则可以解决这个问题,相关代码:[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
题外话:基于GCD实现的定时器,不但不受RunLoop的mode影响,精确度也比NSTimer高。
5、CFRunLoopObserverRef 代表观察者,能否监听RunLoop对象的状态变化。RunLoop拥有的状态如下:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop:1
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer:2
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source:4
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠:32
kCFRunLoopAfterWaiting = (1UL << 6), // 即将从休眠中唤醒:64
kCFRunLoopExit = (1UL << 7), // 即将从Loop中退出:128
kCFRunLoopAllActivities = 0x0FFFFFFFU // 监听全部状态改变
}
这5个类之间的关系:一个RunLoop对象(CFRunLoopRef),有多个Mode(CFRunLoopModeRef),而每个Mode下又有多个输入源(CFRunLoopSourceRef),定时器源(CFRunLoopTimerRef),观察者(CFRunLoopObserverRef).RunLoop对象在Mode之间切换时,需要退出循环,重新进入,以保证每个Mode下的输入源、定时器源,观察者之间不受影响。
最后,说一下RunLoop的用途:
1、NSTimer及延伸使用:[NSObject performSelector],CADisplayLink,
2、让子线程永不退出
3、AutoreleasePool,自动释放池
4、UI更新,setNeedsDisplay/setNeedsLayout
5、NSURLConnection