1、RunLoop 是什么?/ RunLoop 的概念
2、NSRunLoop 和 CFRunLoopRef?
3、RunLoop 和线程的关系?
4、RunLoop对外接口 / RunLoop的几个类?
5、RunLoop内部逻辑?
6、RunLoop应用场景?
RunLoop 是与线程相关的基础架构中的一部分,他是一个处理事件的循环(线程进入这个循环,运行事件处理程序来响应传入的事件)。RunLoop 的目的是当有事件需要处理时,线程是活跃的、忙碌的,当没有事件后,线程进入休眠。
我们不能在一个线程中去操作另外一个线程的run loop对象,那很可能会造成意想不到的后果。不过幸运的是CoreFundation中的不透明类CFRunLoopRef是线程安全的,而且两种类型的run loop完全可以混合使用。Cocoa中的NSRunLoop类可以通过实例方法:
- (CFRunLoopRef)getCFRunLoop;
获取对应的CFRunLoopRef类,来达到线程安全的目的。
CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。
RunLoop和线程是一一对应的关系,从上篇 的底层分析可以知道。在 CFRunLoopRef 里面。创建一个字典,创建一个RunLoop,然后与线程进行绑定 dict[@"pthread_main_thread_np"] = mainLoop。通过 key cvalue 的形式将 字典 和 线程 与 创建的运行循环 RunLoop 进行绑定。默认是主线程、主运行循环,判断没有线程的时候就默认是主线程。
在任何一个Cocoa程序的线程中,都可以通过:
NSRunLoop *runloop = [NSRunLoopcurrentRunLoop];
来获取到当前线程的run loop。
在 coreFoundation 里面关于RunLoop 有5个类:
CFRunLoopRef
CFRunLoopModeRef
CFRUnLoopTimerRef
CGRunLoopObserverRef
其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。他们的关系如下:
一个RunLoop 包含多个mode
一个mode 包含多个item(source、timer、Observer)
每次调用RunLoop的时候,只能指定其中一个mode,需要切换mode 的时候需要先退出loop,然后从新指定一个Mode 进入。为了分割开不同item (source、timer、Observer),让其互不影响。
CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。
source0 只包含了一个回调(函数指针),它并不会主动触发事件。使用时,需要先调用 CFRunLoopSourceSignal(Source),将这个Source标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop)来唤醒RunLoop,让其处理这个事件。
Source1 包含了一个mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop。
Observer 观察者,包含一个回调(函数指针),当RunLoop 的状态发生变化时,观察者就能通过回调接受这个变化。
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
};
内部是一个do while 循环。当调用CFRunLoopRun()时,线程就会一直停留在这个循环里,直到超时或者手动停止或完成。该函数才会返回。
CFRunLoopRunSpecific 1、10
CFRunLoopRun 2-9
1、通知 Observer:即将进入 loop - Observer
2、通知 Observer:将要处理 Timer - Observer
3、通知 Observer:将要处理 Source0 - Observer
4、处理 Source0 - Source0
5、如果有 Source1,跳到第9步 - Source1
6、通知 Observer:线程即将休眠 - Observer
7、休眠,等待唤醒 (source0(port)、Timer、外部h手动唤醒)
8、通知 Observer:线程刚被唤醒 - Observer
9、处理唤醒时收到的消息,之后跳回2 - Timer source1
10、通知 Observer:即将推出 Loop - Observer
6.1、AutoreleasePool
程序启动后系统在主线程的 RunLoop 里面注册了两个 Observer。其回调是_wrapRunLoopWithAutoreleasePoolHandler()。
第一个 Observer 监听的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。order 是 -2147483647 优先级最高,保证创建释放池发生在其他所有回调之前
第二个 Observer 监听两个事件:
1、BeforWaiting(准备进入休眠)时调用 _objc_autoreleasePoolPop() _objc_autoreleasePoolPush()释放旧的池并创建新池;
2、Exit (即将推出Loop)时调用 _objc_autoreleasePoolPop()来释放自动释放池。这个 Observer 的 order 是247483647 优先级最低,保证其释放池发生在所有回调之后。
6.2、定时器
NSTimer 。一个NSTimer 注册到RunLoop 后,RunLoop会为其重复的时间点注册好事件。NSTimer 是依赖与mode 的,如果运行循环中的mode 切换了,那么这个时候NSTimer是不会调用的,当再次切换回对应的mode的时候才会继续掉调用。这也就是NSTimer 不是实时性,不准确的原因。
6.3、PerformSelecter
当调用NSObject 的performSelector:afterDelay: 后,实际上其内部会创建一个TimerT并添加到当前 RunLoop中,如果当前线程RunLoop 没有启动,那么这个方法将失效。
当调用perfromSelector:onThread:时,实际上其会创建一个 Timer 加到对应的线程去,同样的如果对应线程没有启动 RunLoop 该方法也会失效
6.4、事件响应
系统注册了一个 Source1(基于mach port的)用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。
Source1 内核调用其来唤醒RunLoop。
已经其他一些应用。
UI修改、网络、GCD等
这里只说一个官方推荐的方法。
启动RunLoop 时设定时限,RunLoop将一直运行知道事件到达或者分配的时间到期。如果时间到达,则该事件分配给处理程序进行处理,然后退出此次 RunLoop。可以通过重新启动 RunLoop 处理下一个事件。荣阳如果分配的时间到期。也可以重新启动 RunLoop 来处理。这种方式可以指定 RunLoopMode
#import "LJLRunLoopInterviewVC.h"
@interface LJLRunLoopInterviewVC ()
@property (nonatomic, strong) NSThread *longLifeThread;
@property (nonatomic, assign) CFRunLoopRef runLoopRef;
@property (nonatomic, assign) CFRunLoopSourceRef runLoopSourceRef;
@end
- (void)viewDidLoad
{
[super viewDidLoad];
self.longLifeThread = [[NSThread alloc] initWithTarget:self selector:@selector(startUpLongLifeThread) object:nil];
self.longLifeThread.name = @"Long Life Thread";
[self.longLifeThread start];
}
- (void)startUpLongLifeThread
{
NSLog(@"Long Life Thread Start Up");
//将源或计时器添加到RunLoop中,否则RunLoop启动完成后立刻结束
_runLoopRef = CFRunLoopGetCurrent();
CFRunLoopSourceContext sourceContext = {};
sourceContext.info = (__bridge void *)(self);
sourceContext.perform = &RunLoopInputSourcePerformRoutine;
_runLoopSourceRef = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &sourceContext);
CFRunLoopAddSource(_runLoopRef, _runLoopSourceRef, kCFRunLoopDefaultMode);
BOOL done = NO;
do {
CFRunLoopRunResult result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 5, YES);
if (result == kCFRunLoopRunStopped || result == kCFRunLoopRunFinished) {
done = YES;
}
} while (done == NO);
CFRelease(_runLoopSourceRef);
}
static void RunLoopInputSourcePerformRoutine(void *info)
{
LJLRunLoopInterviewVC *vc = (__bridge LJLRunLoopInterviewVC *)(info);
[vc hello];
}
- (void)hello
{
NSLog(@"Hello Thread %@", [[NSThread currentThread] name]);
}
- (void)dealloc
{
if (_runLoopRef != NULL) {
_runLoopRef = NULL;
}
if (_runLoopSourceRef != NULL) {
CFRunLoopSourceInvalidate(_runLoopSourceRef);
_runLoopSourceRef = NULL;
}
if (self.longLifeThread != nil) {
[self.longLifeThread cancel];
self.longLifeThread = nil;
}
}
参考:
https://www.cnblogs.com/jiangzzz/p/5619512.html
https://www.jianshu.com/p/413811babe1e