一、讲讲RunLoop,项目中有用到过吗?
https://github.com/cimain/CoreFoudation/blob/master/
RunLoop的源码地址可以从这个网站进行查看
1.什么是RunLoop
顾名思义,就是运行循环,在程序运行过程中循环做一些事情
2.应用范畴
1)定时器(Timer),PerformSelector
2)GCD Async Main Queue
3)事件响应、手势识别、界面刷新
4)网络请求
5)AutoreleasePool
3.Runloop的实现
int main(int argc,char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc,argv,nil,NSStringFormClass([AppDelegate class]));
}
}
int main(int argc,char *argv[]) {
@autoreleasepool {
int reVal = 0;
do{
//睡眠中等待消息
int message = sleep_and_wait();
//处理消息
reVal = process_message(message);
}while(reVal == 0)
return 0;
}
}
4.RunLoop的作用
程序并不会马上退出,而是保持运行的状态
1)保持程序的持续运行
2)处理App中的各种事件(比如触摸事件、定时器事件等)
3)节省CPU资源,提高程序性能:该做事时做事,该休息时休息
5.获取RunLoop对象
1)使用oc获取对象
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
获取主线程的RunLoop [NSRunLoop mainRunLoop]
2)使用C获取对象
CFRunLoopRef *runLoop = CFRunLoopGetCurrent();
获取主线程的RunLoop CFRunLoopGetMain()
6.RunLoop与线程
1)每条线程都有唯一的一个与之对应的RunLoop对象
2)RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
3)线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建(主线程中UIApplication方法会获取到RunLoop对象,所以主线程默认是有RunLoop的)
4)RunLoop会在线程结束时销毁
5)主线程的RunLoop已经自动获取,子线程默认没有开启RunLoop
7.RunLoop中的类
Core Foundation中关于RunLoop的5个类
1)CFRunLoopRef
2)CFRunLoopModeRef
3)CFRunLoopSourceRef
4)CFRunLoopTimerRef
5)CFRunLoopObserverRef
8.CFRunLoopModeRef
1)CFRunLoopModeRef代表RunLoop的运行模式
2)一个RunLoop包含若干个mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
3)RunLoop启动时只能选择其中一个mode,作为currentMode
4)如果需要切换mode,只能退出当前的mode,再重新选择一个mode进入,不同组的Sources0/Sources1/Timer/Observer能分隔开,互不影响
5)如果mode里没有任何Sources0/Sources1/Timer/Observer,RunLoop会立马退出
9.CFRunLoopModeRef
常用的模式有两种
1)kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App默认的mode,通常主线程是在这个mode下运行
2)UITrackRunLoopMode:界面跟踪Mode,用于ScrollView跟踪触摸滑动,保证界面滑动时不受其他mode影响
NSLog(@"%p %p",[NSRunLoop mainRunLoop],[NSRunLoop currentRunLoop]);
NSLog(@"%p %p",CFRunLoopGetCurrent(),CFRunLoopGetMain());
可以打印出这个对象的地址,但是发现通过C和OC获取到的地址不一样,其他OC是在C上的一层封装,底层还是C的
NSLog(@"%@",[NSRunLoop mainRunLoop]);
可以打印出来这个对象里面拥有的东西
切换mode不会导致程序退出
10.RunLoop的运行逻辑
使用bt可以打印出这个函数栈
1)Source0
a.处理触摸事件
b.performSelector : onThread
2)Source1
a.基于Port的线程间通信
b.系统事件捕捉
3)Timers
a.NSTimer
b.performSelector:withObject:afterDelay:
4)Observers
a.用于监听RunLoop的状态
b.UI刷新(BeforeWaiting)
c.Autorelease pool(BeforeWaiting)
运行逻辑:
1)通知Observers:进入RunLoop
2)通知Observers:即将处理Timers
3)通知Observers:即将处理Sources
4)处理Blocks
5)处理Source0(可能会再次处理Blocks)
6)如果存在Source1,就跳转到第8步
7)通知Observers,开始休眠(等待消息唤醒)
8)通知Observers:结束休眠(被某个消息唤醒)
a.处理timer
b.处理GCD、Async To Main Queue
c.处理Source1
9)处理Blocks
10)根据前面的执行结果,决定如何操作
a.回到第二步
b.退出loop
11)通知Observers,退出loop
11.添加Observers监听RunLoop的所有状态
12.RunLoop休眠的实现原理
内核层面的API
mach_msg
应用层面的API
13.RunLoop在实际开发中的应用
1)控制线程的生命周期(线程保活)
2)解决NSTimer在滑动时停止工作的问题
3)监控应用卡顿
4)性能优化
二、RunLoop内部实现逻辑?
程序的入口函数是CFRunLoopRunSpecific
1.通知Observers进入Loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
2.具体要做的事情
//具体要做的事情
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
3.通知Observers,退出Loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
CFRunLoopRun的具体实现
2)通知Observers,即将处理Timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
3)通知Observers,即将处理Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
4)处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
5)处理Sources0 如果返回值为Yes的话,再次处理Blocks
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}
6)处理有无Source1,如果有Source1,就进行跳转handle_msg
//处理有无Source1
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
//如果有Source1,就进行跳转handle_msg
goto handle_msg;
}
7)通知Observers,即将休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
8)通知Observers,结束休眠
__CFRunLoopUnsetSleeping(rl);
处理Timers
//处理Timers
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
处理GCD
//处理GCD
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
处理Source1
//处理Source1
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
9)再次处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
10)根据前面的执行结果判断执行何种操作
三、RunLoop和线程的关系
1.每条线程都有唯一的一个与之对应的RunLoop对象
2.RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
3.线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建(主线程中UIApplication方法会获取到RunLoop对象,所以主线程默认是有RunLoop的)
4.RunLoop会在线程结束时销毁
5.主线程的RunLoop已经自动获取,子线程默认没有开启RunLoop
四、timer和RunLoop的关系?
五、程序中添加每3秒响应一次的NSTimer,当拖动tableView时timer可能无法响应要怎么解决?
六、RunLoop是怎么响应用户操作的,具体流程是什么样的?
由Source1把系统事件给捕捉,Source1会将这个事件包装成事件队列,这个事件队列又在Source0里面
七、说说RunLoop的几种状态?
八、RunLoop的mode作用是什么?
1)kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App默认的mode,通常主线程是在这个mode下运行
2)UITrackRunLoopMode:界面跟踪Mode,用于ScrollView跟踪触摸滑动,保证界面滑动时不受其他mode影响
九、RunLoop的其他知识补充
1.RunLoop的概念
1)RunLoop是消息循环,每一个线程内部都有一个消息循环,一一对应,以键值对存储的,只有主线程的消息循环默认开启,子线程的消息循环默认不开启。
2)RunLoop是事件处理循环,类似于while,for,while(1)可以用来描述,但是不准确,因为while(1)是忙等状态,会消耗资源,而RunLoop没有消息的时候会休眠,有消息才会唤醒。
3)RunLoop实际上是一个对象,这个对象在循环中用来处理程序运行过程中出现的各种事件(比如说触摸事件、UI刷新事件、定时器事件、Selector事件...),从而保持程序的持续运行
4)RunLoop在没有事件处理的时候,会使线程进入休眠状态,从而节省CPU资源,提高程序性能。
5)RunLoop与线程是息息相关的,我们知道线程的作用是用来执行特定的一个或者是多个任务,在默认情况下,线程执行完之后就会退出,就不能在执行任务了,这时我们就需要采取一种方式来让线程能够不断的处理任务,并不退出,所以我们有了RunLoop。
6)RunLoop并不能保证线程安全,我们只能在当前线程内部操作当前线程的RunLoop对象,而不能在当前线程内部去操作其他线程的RunLoop对象。
7)RunLoop对象在第一次获取RunLoop时创建,销毁是在线程结束的时候。
8)主线程的RunLoop对象系统自动帮助我们创建好了,而子线程的RunLoop对象需要我们主动创建和维护
2.RunLoop的作用
1)保证程序不退出
2)负责处理输入事件
3)如果没有事件发生,会让程序进入休眠状态
3.RunLoop可以做什么?
1)卡顿检测
2)线程保活
3)防止Crash
4)和线程交互的时候,使用RunLoop
4.线程保活
案例一:
在子线程中在创建子线程,实现方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"Hello!%@",[NSThread currentThread]);
//这个函数注册了一个timer,在子线程
[self performSelector:@selector(doSomething) withObject:nil afterDelay:0];
NSLog(@"ZS%@",[NSThread currentThread]);
});
}
- (void)doSomething {
NSLog(@"Logic,%@",[NSThread currentThread]);
}
打印结果:
此时doSomething中的方法不执行,因为我们开启了一个异步线程,主线程的消息循环默认开启,可以执行,但是在子线程中添加任务,又开启了一个子线程,子线程的消息循环不开启,所以不执行 。
案例二:
线程保活
我们使用NSThread,或者GCD开启的线程会自动销毁,生命周期由系统来管理
创建一个按钮,点击按钮时执行下面的方法,自定义thread,让他继承自NSThread,然后重新delloc方法
- (IBAction)beginAction:(id)sender {
thread *ts = [[thread alloc]initWithTarget:self selector:@selector(doThreadSomeThing) object:nil];
[ts start];
}
- (void)doThreadSomeThing {
NSLog(@"start");
NSLog(@"%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2];
NSLog(@"end");
}
此时的打印结果为:
线程执行完毕之后,自动销毁。如果我们再进行点击会重新创建一个新的线程。(不会自动创建线程,会自动销毁线程)