RunLoop的概念
RunLoop是一个对象,这个对象在循环中用来处理程序运行过程中出现的各种事件(比如说手势识别、UI刷新事件、定时器事件、事件响应、Selector事件、网络请求、AutoreleasePool),从而保持程序的持续运行;而且在没有事件处理的时候,会进入睡眠模式,从而节省CPU资源,提高程序性能。
void CFRunLoopRun(void) {
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
从上面函数可以看出,线程执行了这个函数后,就会一直处于这个函数内部 "接收消息->处理->等待-接收消息" 的循环中,直到这个循环结束
我们新建一个项目证明程序启动是否默认开启了RunLoop
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"开始---%@",[NSThread currentThread]);
int result = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"结束---%@",[NSThread currentThread]);
return result;
}
}
// 打印结果
// 开始---{number = 1, name = main}
运行程序,我们发现只会打印开始,并不会打印结束,这再次说明在UIApplicationMain函数中,开启了一个和主线程相关的RunLoop,导致UIApplicationMain不会返回0,保持运行状态,不让程序退出。
RunLoop对象
- iOS中有2套API来访问和使用RunLoop
- Foundation: NSRunLoop (对CFRunLoopRef的一层OC包装)
- Core Foundation: CFRunLoopRef (开源的)
//Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
//Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
RunLoop与线程的关系
- 每条线程都有唯一的一个与之对应的RunLoop对象
- RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
- 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
- RunLoop会在线程结束时销毁
- 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
Core Foundation中关于RunLoop的5个类
FRunLoopRef //获得当前RunLoop和主RunLoop
CFRunLoopModeRef //运行模式,只能选择一种,在不同模式中做不同的操作
CFRunLoopSourceRef //事件源,输入源
CFRunLoopTimerRef //定时器时间
CFRunLoopObserverRef //观察者
内部结构
1.CFRunLoopRef的类型:
>每个CFRunLoopRef 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
>CFRunLoopRef获取Mode的接口:
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);
CFRunLoopRef的结构:
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set 存放通用模式(kCFRunLoopDefaultMode、UITrackingRunLoopMode)
CFMutableSetRef _commonModeItems; // Set 存放放入_commonModes的timer\soure0\soure1
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set 存放CFRunLoopModeRef
...
};
2.CFRunLoopModeRef的类型
>1. kCFRunLoopDefaultMode
App的默认Mode,通常主线程是在这个Mode下运行
>2. UITrackingRunLoopMode:
界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
>3. UIInitializationRunLoopMode:
在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
>4. GSEventReceiveRunLoopMode:
接受系统事件的内部 Mode,通常用不到
>5. kCFRunLoopCommonModes:
这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
CFRunLoopModeRef的结构:
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
3.CFRunLoopSourceRef
>1. 是事件产生的地方。Source有两个版本:Source0 和 Source1。
>2. Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
>3. Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程.
4. CFRunLoopObserverRef
>基于时间的触发器,基本上说的就是NSTimer()
>1.NSTimer会受到runloop的mode影响
>2.GCD的定时器不受runloop的mode影响
5. CFRunLoopObserverRef
>CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变。
我们直接来看代码,给RunLoop添加监听者,监听其运行状态:
// 创建监听者
/*
第一个参数 CFAllocatorRef allocator:分配存储空间 CFAllocatorGetDefault()默认分配
第二个参数 CFOptionFlags activities:要监听的状态 kCFRunLoopAllActivities 监听所有状态
第三个参数 Boolean repeats:YES:持续监听 NO:不持续
第四个参数 CFIndex order:优先级,一般填0即可
第五个参数 :回调 两个参数observer:监听者 activity:监听的事件
*/
/*
所有事件
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7),// 即将退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop进入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop要处理Timers了");
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添加监听者
/*
第一个参数 CFRunLoopRef rl:要监听哪个RunLoop,这里监听的是主线程的RunLoop
第二个参数 CFRunLoopObserverRef observer 监听者
第三个参数 CFStringRef mode 要监听RunLoop在哪种运行模式下的状态
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
/*CF的内存管理(Core Foundation)
凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release GCD本来在iOS6.0之前也是需要我们释放的,6.0之后GCD已经纳入到了ARC中,所以我们不需要管了*/
CFRelease(observer);
RunLoop运行逻辑
-
Source0
-
触摸事件处理
-
performSelector:onThread:
-
-
Source1
-
基于Port的线程通信
-
系统事件捕捉
-
Timers
-
-
NSTimeer
-
performSelector:withObject:afterDelay:
-
-
Observers
-
用于监听RunLoop的状态
-
UI刷新(BeforeWaiting)
-
Autorelease pool(BeforeWaiting)
-
从源代码来证明
void CFRunLoopRun(void) {
int32_t result;
do {
// 用DefaultMode启动
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
// Runloop的主要实现(我们删减一些不必要的代码)
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
// 1. 通知 Observers: RunLoop 即将进入 loop。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 2. 内部函数,进入loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 3. 通知 Observers: RunLoop 即将退出 loop。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do {
// 2. 通知 Observers: RunLoop 即将触发 Timer 回调
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 4. 执行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
// 5. RunLoop 触发 Source0 (非port) 回调
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
// 执行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
// 6. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
// goto 8
goto handle_msg;
}
// 7. 通知 Observers: RunLoop 的线程即将进入休眠(sleep)
if (!sourceHandledThisLoop) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
// • 一个基于 port 的Source 的事件。
// • 一个 Timer 到时间了
// • RunLoop 自身的超时时间到了
// • 被其他什么调用者手动唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// 8. 通知 Observers: RunLoop 的线程结束休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 收到消息,处理消息
handle_msg:;
if (msg_is_timer) {
// 8.1 如果被Timer唤醒,触发Timer的回调
(!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time()))
} else if (livePort == dispatchPort) {
// 8.2 如果被GCD唤醒,触发GCD的回调
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else {
// 8.3 如果被source1唤醒,触发source1回调
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
}
// 9 执行被加入的block
__CFRunLoopDoBlocks(rl, rlm);
// 10 设置返回值
if (sourceHandledThisLoop && stopAfterHandle) {
// 进入loop时参数说处理完事件就返回
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
// 超出传入参数标记的超时时间了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
// 被外部调用者强制停止了
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
// 11. 最后hi根据retVal的值,通知 Observers: RunLoop 即将退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return retVal;
}
关于线程保活
比如开发过程有很多这样的需求,保持一个线程的生命周期,让次线程一直处理事件
#import "ViewController.h"
#import "LPThread.h"
@interface ViewController ()
@property (strong, nonatomic) LPThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.stopped = NO;
self.thread = [[LPThread alloc] initWithBlock:^{
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.isStoped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (!self.thread) return;
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 子线程需要执行的任务
- (void)test
{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
// 子控制器创建一个按钮:点击按钮停止子线程RunLoop
- (IBAction)stop {
if (!self.thread) return;
// 在子线程调用stop
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}
// 用于停止子线程的RunLoop
- (void)stopThread
{
// 设置标记为NO
self.stopped = YES;
// 停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)dealloc
{
NSLog(@"%s", __func__);
}
定时器
NSTimer 其实就是 CFRunLoopTimerRef,一个 NSTimer 注册到 RunLoop 后,RunLoop 会为其重复的时间点注册好事件。下面两段代码对应不同的场景
// 系统自动创建RunLoop,mode模式是kCFRunLoopDefaultMode模式,如果滑动页面定时器就是停止
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"我是系统添加NSRunLoopMode定时器");
}];
//-------------------------------------------------------
// 自定义RunLoop的mode模式 NSRunLoopCommonModes,kCFRunLoopDefaultMode、UITrackingRunLoopMode都能运行
// NSRunLoopCommonModes并不是一种真的模式,只是一种标记,内部存放mode
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"我是自定义NSRunLoopMode定时器");
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//-------------------------------------------------------
PerformSelecter
当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
关于GCD