一. Runloop基本作用
1)保持程序的持续运行
处理APP中各种事件(比如触摸事件,定时器事件,selector事件)
节省cup资源,提高程序性能:该做事时做事,该休息时休息。
main函数中的runLoop
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
代码UIApplicationMain函数内部启动了一个runLoop,所以UIApplicationMain函数一直没有返回,保持程序的持续运行。这个默认启动runloop是跟主线程关联的。
点击UIApplicationMain这个函数我们发现,它是有返回值的, UIKIT_EXTERN int UIApplicationMain(int argc, char * _Nonnull * _Null_unspecified argv, NSString * _Nullable principalClassName, NSString * _Nullable delegateClassName);
,这里我们可以测试一下,UIApplicationMain函数内部是一直执行的,是启动了一个runloop的,测试代码如下:
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"-------star--------");
int number = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"number:%d",number);
return number;
}
}
控制台输出如下:
2018-09-22 18:08:16.417670+0800 123Demo[919:39945] -------star--------
这里我们发现我们的number并没有被输出,因为UIApplicationMain函数一直没有返回,UIApplicationMain函数内部一直在执行。
二. RunLoop对象
iOS中有两套API来访问和使用RunLoop
- Foundation NSRunLoop
- Core Foundation CFRunLoopRef
Runloop相关资料 https://opensource.apple.com/source/CF/CF-1151.16/
链接 https://pan.baidu.com/s/14xMqWH47X5tex0KPjxkMAw 密码:lx8u
三. RunLoop与线程
- Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的NSRunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的NSRunLoop对象
- Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的NSRunLoop对象
CFRunLoopGetMain(); // 获得主线程的NSRunLoop对象
这里,我们可以看下CFRunLoop.c里面的核心源码
// Do any additional setup after loading the view, typically from a nib.
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
// 创建字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 创建主线程对应的runloop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 使用字典保存主线程-主线程对应的runloop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 从字典中获取子线程的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
// 如果子线程的runloop不存在,那么就为该线程创建一个对应的runloop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 把当前子线程和对应的runloop保存到字典中
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
- Tips
每条线程都有唯一的一个与之对应的RunLoop对象.
主线程RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
RunLoop在第一次获取是创建,在线程结束时销毁
三. RunLoop相关类
- Core Foundation中关于RunLoop的5个类
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
在RunLoop中有多个运行模式,但是RunLoop只能选择一种运行模式(如:一台空调有制冷和制热两种模式,但是启动后只能选择一种模式)。
CFRunLoopModeRef代表RunLoop的运行模式:
一个RunLoop包含若干个Mode,每个Mode又包含若干个source/timer/oberrver。model里面至少要有一个
timer
或者是source
每次RunLoop启动时,只能选择其中一个mode,这个mode被称作currentMode.
如果需要切换mode,只能退出Loop,再重新指定一个Mode进入
CFRunLoopModeRef系统默认注册了5个Mode:
kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
4: GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
5: kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
kCFRunLoopCommonModes模式有一个比较经典的例子,实现一个精准的timer。
例:一个tableview上有一个定时器,这时应该用kCFRunLoopCommonModes模式。
- (void)timer {
// 1.创建定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 2.添加定时器到runloop中
/**
第一个参数:定时器
第二个参数:runloop的运行模式
*/
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void)run {
NSLog(@"run----%@ --- %@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode);
}
- Tips
定时器在子线程中创建,该定时器不会工作,需要run开启
主线程RunLoop默认自动创建,子线程的RunLoop需要主动创建
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
......定时器.....
//开启runloop
[currentRunLoop run];