01基本认识
什么是RunLoop
顾名思义
运行循环
在程序运行过程中循环做一些事情
当我一启动IOS程序的时候,他内部就会创建一个RunLoop对象,循环做一些事情
- 应用范畴
定时器(Timer)、PerformSelector
GCD Async Main Queue
事件响应、手势识别、界面刷新
网络请求
AutoreleasePool
如果没有RunLoop程序执行完main函数就会退出,
为了保证程序一直在运行之后,他就引入了RunLoop,当我们执行main函数调用UIApplicationMain的时候,就会创建RunLoop对象。
他内部大概是这个样的
int retVal = 0;
do {
//睡眠中等待消息
int message = sleep_and_wait();
//处理消息
retVal = process_message(message)
} while (retVal == 0);
当执行完main之后,程序不会马上退出,而是保持运行状态
RunLoop的基本作用
保持程序的持续运行
处理App中的各种事件(比如触摸事件、定时器事件等)
节省CPU资源,提高程序性能:该做事时做事,该休息时休息
02获取RunLoop对象
IOS没有RunLoop会马上退出,执行main返回0,程序立即退出。
RunLoop有两个,一个是OC的一个是C语言的
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
CFRunLoopRef runloopR = CFRunLoopGetCurrent();
iOS中有2套API来访问和使用RunLoop
Foundation:NSRunLoop
Core Foundation:CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表着RunLoop对象
NSRunLoop是基于CFRunLoopRef的一层OC包装
CFRunLoopRef是开源的
https://opensource.apple.com/tarballs/CF/
Runloop与线程的关系
1.每条线程都有唯一的一个与之对应的RunLoop对象
2. RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
3. 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
4.RunLoop会在线程结束时销毁
5. 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
NSLog(@"%p--%p", CFRunLoopGetCurrent(), CFRunLoopGetMain());
0x600001e6cc00--0x600001e6cc00
NSLog(@"%p--%p", [NSRunLoop currentRunLoop], [NSRunLoop mainRunLoop]);
0x600000660000--0x600000660000
打印结果不一样是因为NSRunLoop是对CFRunLoop的包装,他里面存放着CFRunLoop,一个线程对应唯一一个runloop
通过查看源码,我们可以看出当我们执行CFRunLoopGetCurrent-> CFRunLoopGet0->里面有个CFGetDictionaryValue取RunLoop,根据一个thread的key,如果一开始runloop对象不存在,他就创建一个runloop,然后把它添加到字典之中
03CFRunLoopModeRef
如果要寻做一些事,仅仅有这个RunLoop对象是不行,他还要以来其他的对象
Core Foundation中关于RunLoop的5个类
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
CFRunLoop的结构

可以看出,他和线程是一一对应的关系,他有一个集合存放着他的mode,这里面只有一个mode是currentMode,
mode的结构

source0和source1里面装的CFRunLoopSourceRef这种对象
observers里面装的是CFRunLoopObserverRef这种对象

- 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 影响
04CFRunLoopModeRef的成员
通过控制台bt可以打印函数调用栈
Source0
触摸事件处理
performSelector:onThread:
Source1
基于Port的线程间通信
系统事件捕捉,比如点击屏幕产生的事件就是他捕捉到的,然后再交给source0处理
Timers
NSTimer
performSelector:withObject:afterDelay:
Observers
用于监听RunLoop的状态
UI刷新(BeforeWaiting),当前听器间听到runloop要睡觉了,他就会进行一次UI页面刷新
比如设置颜色,当你值完代码,他先保留住,一旦监听到睡觉,就是刷新
Autorelease pool(BeforeWaiting)//他在什么时候释放对象,一旦监听到要睡觉了,就清理一下自动释放的对象
05CFRunLoopObserverRef
Observer

监听runloop的状态Observer,没有OC的API我们只能通过CF的函数形式添加Observer
手动创建一个Observer监听runloop的状态
void observerCall(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
//监听状态
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
//创建一个observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observerCall, NULL);
//添加一个observer
//kCFRunLoopCommonModes
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
CFRelease(observer);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"------");
}
监听模式的改变,可以拖一个scrollView拖动一下改变的runloop的mode
//监听状态
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry---%@",CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()));
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit---%@", CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()));
break;
default:
break;
}
//log
kCFRunLoopExit---kCFRunLoopDefaultMode
2020-10-26 21:56:57.104392+0800 RuntimeIsa[1573:89315] kCFRunLoopEntry---UITrackingRunLoopMode
2020-10-26 21:56:57.315033+0800 RuntimeIsa[1573:89315] kCFRunLoopExit---UITrackingRunLoopMode
2020-10-26 21:56:57.315199+0800 RuntimeIsa[1573:89315] kCFRunLoopEntry---kCFRunLoopDefaultMode
06答疑
循环做把mode里面source拿出来处理,
source里面的东西是不固定的
07执行流程图
在IOS我们表面上能看到的Runloop就是applicationMain
要想看到他具体做了什么事情,就要分析源码,但是源码太过于抽象,纯c。可以先看下总结图片。他大概就是不断的切换mode,然后处理不同模式喜爱的source0 ,source1,timer等
handleport就是source1 可能是别的线程发过来的信息


08-源码分析
通过打断点,在控制台输入bt
指令,来打印省略的步骤可以查看到他调用了一个CFRunLoopSpecific的函数->CFRunloopRun这个函数里面就是他在做的处理,就是一个do-while循环
__CFRunLoopRef rl, CFRunLoopModeRef rum
int32_t retVal = 0;
do
{
//通知Obervers,即将处理Timers
__CFRunLoopDoObservers(rl,rlm,kCFRunLoopBeforeTimers);
//通知Obervers,即将处理Sources
__CFRunLoopDoObservers(rl,rlm,kCFRunLoopBeforeSources);
//处理Blocks
__CFRunLoppDoBlocks(rl,rlm)
//处理source0
if (__CFRunloopDoSource0(rl,rlm,stopAfterHandle))
{
//处理block;
}
}while(0 == retVal);
//判断有无source1,如果有就跳到,handle_msg
//通知observer即将进入休眠
__CFRunLoopDoObservers(rl,rlm,kCFRunLoopBeforeSleeping);
__CFRunLoopSetSleeping(rl);
//等待别的消息来唤醒当前线程
__CFRunLoopServiceMachPort
handle_msg:
if(被time唤醒)
{
//处理timers
}else if(被GCD唤醒)
{
//处理GCD
}else
{//被source1唤醒,
//处理source1
}
//处理block
//设置返回值
如果返回值retVal还是 == 0;
那么就会回到开头从新循环
09调用细节
我们平时所做的UI界面刷新,timer定时器的监听,自动释放池都是疣runloop来做控制的,它主要做这几件事件,1 doObservers, 2 doBlocks 3. doSource03. doSource01 4doTimers5,处理gcd 6 休眠,这些函数里面还会调用一个__CFRUNLOOP_IS_CALLING_TO开头的函数
- GCD有自己的逻辑,他很多东西不依赖RunLoop,他有一种情况会交给runloop处理
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//处理子线程的逻辑
dispatch_async(dispatch_get_main_queue(), ^{
//回到住线程刷新界面
});
});
01、通知Observers:进入Loop
02、通知Observers:即将处理Timers
03、通知Observers:即将处理Sources
04、处理Blocks
05、处理Source0(可能会再次处理Blocks)
06、如果存在Source1,就跳转到第8步
07、通知Observers:开始休眠(等待消息唤醒)
08、通知Observers:结束休眠(被某个消息唤醒)
01> 处理Timer
02> 处理GCD Async To Main Queue
03> 处理Source1
09、处理Blocks
10、根据前面的执行结果,决定如何操作
01> 回到第02步
02> 退出Loop
11、通知Observers:退出Loop
10-休眠的细节
开始睡觉意味着当前线程不做事情了,相当于阻塞了,这个阻塞和我们所写的while(1)(并没有休眠,线程还是在做事情)这种不一样,他是线程休息了什么代码也不执行了
通过调用mach_msg来调用内核层面的API实现线程的休眠的目的
当我们在用户态执行了mach_msg函数之后,他会进入内核态,调用内核态的mach_msg的实现,来达到休眠的目的。没消息就让线程休眠,有消息就唤醒线程。回到用户态处理消息,通过状态切换真正达到了让线程休息

runloop是怎么响应用户的操作的?
首先它是由source1来把用户事件捕捉,点击。source1会把这个事件,包装成事件队列,放到事件队列里面去。事件队列又是在source0里面处理的
说说runloopd的几种状态
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities =
runloop的mode作用是什么?
常见的2种Mode
kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
mode可以将不同的source0/source1/t/o隔离开来。他们之间相互隔离不受影响
11NSTimer失效
runloop实际开发中的应用,
控制线程生命周期(线程保活),有时候我们不希望子线程运行完就销毁,
解决NSTimer在滑动时停止工作的问题
监控应用卡顿
性能优化
默认下的NSTimer我们一拖拽UI控件,他就会停止运行,因为我们在拖拽的时候,runloop切换了mode,定时器是在默认模式下工作的
//用这个方法创建的定时器是加到默认模式下的
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer)
{
NSLog(@"%i", ++count);
}];
网友评论