RunLoop
2018-06-09 本文已影响17人
MeliodasLy
RunLoop
顾名思义
- 运行循环
- 在程序运行过程中循环做一些事情
- 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的:
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
这种模型通常被称作Event Loop。
实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立即被唤醒。
RunLoop实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面的Event Loop的逻辑。线程执行了这个函数后,就会一直处于正常函数内部“接受消息->等待->处理”的循环中,直到这个循环结束(比如传入quit的消息),函数返回。
应用范畴
- 定时器(Timer)、performSelector
- GCD Async Main Queue
- 事件响应、手势识别、界面刷新
- 网络请求
- AutoreleasePool
RunLoop的基本作用
- 保持程序的持续运行
- 处理App中的各种事件(比如触摸事件、定时器事件等)
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
RunLoop对象
- iOS中有2套API来访问和使用RunLoop
- Foundation:NSRunLoop
- Core Foundation:CFRunLoopRef
- NSRunLoop和CFRunLoopRef都代表着RunLoop对象
- NSRunLoop是基于CFRunLoopRef的一层OC包装(所以runloop的地址会不一样)
- 提供了面向对象的API,但是这些API不是线程安全的
-
CFRunLoopRef是开源的
- 提供了纯C函数的API,所有这些API都是线程安全的
- NSRunLoop是基于CFRunLoopRef的一层OC包装(所以runloop的地址会不一样)
// OC获取当前的RunLoop
NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];
// OC获取主线程RunLoop
NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
// C语言获取当前的RunLoop
CFRunLoopRef currentRunloop2 = CFRunLoopGetCurrent();
// C语言获取主线程的RunLoop
CFRunLoopRef mainRunloop2 = CFRunLoopGetMain();
RunLoop与线程
- 每条线程都有唯一的一个与之对应的RunLoop对象
- RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
- 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
- RunLoop会在线程结束时销毁
- 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
- 只能在一个线程的内部获取其RunLoop(主线程除外)。
RunLoop相关的类
- Core Foundation中关于RunLoop的5个类
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef 事件产生的地方
- CFRunLoopTimerRef
- CFRunLoopObserverRef
CFRunLoopRef
typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
pthread_t _pthread; // RunLoop所对应的线程
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode; // _modes这个集合里面只有一个模式被称为当前模式_currentMode
CFMutableSetRef _modes; // 这个集合里面装了很多CFRunLoopModeRef类型的对象
};
CFRunLoopModeRef
- CFRunLoopModeRef代表RunLoop的运行模式
- 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
- RunLoop启动时只能选择其中一个Mode,作为currentMode
- 如果需要切换Mode,只能退出当前Loop,在重新选择一个Mode进入
- 目的:不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
- 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
常见的2种Mode
- kCFRunLoopDefaultMode(等价于NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
- 另外还有一种模式:kCFRunLoopCommonModes 默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode两种模式
typedef struct __CFRunLoopMode * CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name; // mode的名称
CFMutableSetRef _sources0;
CFMutableSetRef _sources1; // source里面装了很多CFRunLoopSourceRef类型的对象
CFMutableArrayRef _observers;// 里面装了很多CFRunLoopObserverRef类型的对象
CFMutableArrayRef _timers; // 里面装了很多CFRunLoopTimerRef类型的对象
};
CFRunLoopObserverRef
/* RunLoop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),// 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1),// 即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出Loop
kCFRunLoopAllActivities = 0X0FFFFFFFU
};
// 监听事件的状态变化(只有c语言接口)
// 方法一
void observeRunLoopActivities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
}
// 创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActivities, NULL);
// 添加observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放observer
CFRelease(observer);
// 方法二
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopEntry - %@",mode);
CFRelease(mode);
break;
}
case kCFRunLoopExit:{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopExit- %@",mode);
CFRelease(mode);
break;
}
default:
break;
}
});
// 添加observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放observer
CFRelease(observer);
根据源码分析,得到如下关系图
runloopRunLoop的运行逻辑
- Source0
- 触摸事件处理
- performSelector: onThread:(提供一个线程,就可以让对应方法去该线程去执行)
- Source1
- 基于Port的线程间通信
- 系统事件捕捉
- Timers
- NSTimer
- performSelector: withObject: afterDelay:
- Observers
- 用于监听RunLoop的状态
- UI刷新(BeforeWaiting)
- AutoReleasePool(BeforeWaiting)
如下图所示
runloop流程
RunLoop休眠的实现原理
休眠的实现原理实质:从用户态切换到内核态,去等待消息,没有消息就让线程休眠,不在占用cpu,有消息就唤醒线程。
RunLoop在实际开发中的应用
-
控制线程生命周期(线程保活,比如AFNetWorking都是在子线程中等待的,所以是一直保活)
-
解决NSTimer在滑动时停止工作的问题
static int count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d",++count);
}];
// NSDefaultRunLoopMode/UITrackingRunLoopMode才是真正存在的模式
// NSRunLoopCommonModes并不是一个真的模式,它只是一个标记
// timer能在_commonModes数组中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
// NSLog(@"%d",++count);
// }];
- 监控应用卡顿
- 性能优化