iOS课程观看笔记(七)---RunLoop
GCD的内部原来,线程池
RunLoop与RunTime
什么是RunLoop?
RunLoop是通过内部维护的事件循环来对 事件/消息 进行管理的 对象
事件循环包括两点:
没有消息需要处理时,休眠以避免资源占用
有消息需要处理时,立刻被唤醒
RunLoop的关键点,就是用户态与内核态的切换
问:为什么iOS中main()函数可以保持一直运行状态。而不退出?
在main()函数内部,会调用UIApplicationMain()函数,在UIApplicationMain()函数内部会启动主线程的RunLoop,RunLoop通过内部维护的事件循环来对 事件/消息 进行管理,能做到有事做的时候去做事,没事做的时候,通过用户态到内核态的切换,进行休眠,从而避免资源浪费。
RunLoop如何维护事件循环机制呢?
RunLoop的数据结构
苹果提供了两套RunLoop:NSRunLoop和CFRunLoop
CFRunLoop是Core Foundation框架的,CF开头的框架是开源的
NSRunLoop是Foundation框架的
NSRunLoop是对CFRunLoop的封装,提供了面向对象的API
CFRunLoop
CFRunLoopModel
Source/Timer/Observer
CFRunLoopSource
Source0
需要手动唤醒线程
触摸事件处理
performSelector:onThread:
Source1
具备唤醒线程的能力
基于Port的线程间通信
系统事件捕捉
CFRunLoopTimer
基于事件的定时器
和NSTimer是 toll-free bridged(免费桥转换)的
CFRunLoopObserver
问:我们可以监听RunLoop的哪些时间点?
kCFRunLoopBeforeWaiting:waiting等待、休眠。是用户态到内核态的转换
kCFRunLoopAfterWaiting:是内核态到用户态的转换
线程和RunLoop是一对一的关系
RunLoop跟Model是一对多的关系
Model和Source/Timer/Observer也是一对多的关系
一个RunLoop包含若干个Mode,每个Mode又包含若干个source0、source1、observer、timer
RunLoop启动时只能选择其中一个Mode作为currentMode。
如果需要切换Mode,只能退出当前loop,重新选择一个Mode进入。
不同组的source0、source1、observer、timer能分隔开来,互不影响
如果Mode里没有任何source0、source1、observer、timer,RunLoop会立马退出
CFRunLoopCommonModes
CFRunLoopCommonModes并不是实际存在的一种Mode
commonMode是同步Source/Timer/Observer到多个Mode中的一种技术方案
问:点击App图标,从启动到杀死进程,中间经历了哪些?
从大的方面,经历了程序启动的三个方面:dyld、runtime、main函数
还可以从RunLoop的角度去分析
RunLoop和NSTimer
两种方法:
方法一:
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"123");
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
方法二:
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
RunLoop与多线程
线程是和RunLoop是一一对应的
自己手动创建的线程默认是没有RunLoop的
问:怎样实现一个常驻线程?
为当前线程开启一个RunLoop
向该RunLoop中添加一个Port或者Source维护RunLoop的事件循环
启动该RunLoop
#import "MCObject.h"
@implementation MCObject
static NSThread *thread = nil;
// 标记是否要继续事件循环
static BOOL runAlways = YES;
+ (NSThread *)threadForDispatch{
if (thread == nil) {
@synchronized(self) {
if (thread == nil) {
// 线程的创建
thread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequest) object:nil];
[thread setName:@"com.imooc.thread"];
//启动
[thread start];
}
}
}
return thread;
}
+ (void)runRequest
{
// 创建一个Source
CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
// 创建RunLoop,同时向RunLoop的DefaultMode下面添加Source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// 如果可以运行
while (runAlways) {
@autoreleasepool {
// 令当前RunLoop运行在DefaultMode下面
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
}
}
// 某一时机 静态变量runAlways = NO时 可以保证跳出RunLoop,线程退出
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
}
@end
问:怎么保证子线程数据回来更新UI的时候,不打断用户的滑动操作?
滑动是在UITrackingRunloopMode下,滑动结束了,runloop由UITrackingRunloopMode又回到defaultMode下了。
数据加载一般在子线程下载,下载完毕后在主线程进行UI刷新。
可以将子线程数据,给主线程刷新UI的时候,包装后提交到主线程的defaultModel下,这样两个model不会同时执行,也就不会打断用户的滑动操作。
[self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
推荐阅读
-
iOS课程观看笔记(七)---RunLoop
-
Coursera机器学习课程笔记(七)
-
iOS学习笔记-139.RunLoop07——Runloop处理流程
-
iOS于海老师课程观看笔记(一)---UI视图相关
-
iOS学习笔记-139.RunLoop07——Runloop处理流程
-
iOS学习笔记-139.RunLoop08——Runloop应用之常驻线程
-
iOS学习笔记-137.RunLoop05——Runloop相关类3_CFRunLoopTimerRef(NSTimer)为何定时有时会失败
-
iOS学习笔记-138.RunLoop06——Runloop相关类4_ CFRunloopSourceRef和CFRunLoopObserverRef
-
iOS多线程学习笔记(GCD、RunLoop、NSThread)
-
iOS课程观看笔记(六)---多线程