内容简介:所以,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 “接受消息->等待->处理” 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。在
Run loop
表示一个循环程序会一直运行。通过内部维护事件循环( Event Loop
)来对事件/消息进行管理的一个对象。事件循环模型的特点: 在没有消息处理时,线程进入休眠状态以避免资源占用,在有消息时,立刻被唤醒执行任务。
所以,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 “接受消息->等待->处理” 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。
在 iOS
系统中提供了两个这样的对象:
-
NSRunLoop
: 基于CFRunLoopRef
封装,提供面向对象的API
,但是不是线程安全的。 -
CFRunLoopRef
:是在CoreFoundation
框架中,提供了纯 C 的API,是线程安全。
main
函数为什么不会退出?
应用程序中,启动后执行下面代码:
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
因为调用 UIApplicationMain
启动了一个 Runloop
。有接收消息进行处理,没有消息进入等待。
RunLoop 和 线程的关系
RunLoop和线程是一一对应的。
RunLoop NSRunLoop *runloop = [NSRunLoop currentRunLoop];
RunLoop数据结构
在 CoreFoundation
中,提供了5个类:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
他们之间的关系如下:
一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
结构体定义
CFRunLoop
结构体如下:
-
pthread
: 与线程相关,一一对应。 -
currentMode
:当前RunLoop所处的模式 -
modes
集合CFMutableSetRef
-
commonModes
:CFMutableSetRef
-
commonModeItems
: 集合,包含多个Observer
、Timer
、Source
。
Mode
Mode
代表 RunLoop
的不同运行模式,运行时只能选择一种模式运行作为 currentMode
,如果想切换需要退出当前循环,重新选择 Mode
在进入循环。为什么这样设计呢,为了隔离开不同的任务,互不影响。
CFRunLoopMode结构体:
RunLoopMode
中包含的内容:
-
name
: 名称NSDefaultRunLoopMode
-
source0
:处理点击事件,performSelector:onThread:withObject:
方法。 -
source1
:基于Port的线程间通信,系统事件捕捉。 -
observers
数组,监听状态,UI刷新,自动释放池 -
timers
数组,NSTimer
一个 Mode
对应多个 Source/Timer/Observer
。 RunLoop
只能接受到当前Mode中添加的事件。
两种常用的 Mode
:
-
kCFRunLoopDefaultMode(NSDefaultRunLoopMode)
: APP的默认Mode,通常主线程是这个模式。 -
UITrackingRunLoopMode
: 界面跟踪Mode
,用户ScrollView
追踪触摸滑动,保证界面滑动是不受其他Mode
的影响。
注意: NSRunLoopCommonMode
不是实际存在的 Mode
。是同步 Source/Timer/Observer
到多个 Mode
中的一种技术方案。
RunLoop的mode作用是什么?
mode 主要是用来指定事件在运行循环中的优先级的,分为:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode) UITrackingRunLoopMode UIInitializationRunLoopMode NSRunLoopCommonModes(kCFRunLoopCommonModes)
苹果公开提供的 Mode 有两个:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode) NSRunLoopCommonModes(kCFRunLoopCommonModes)
Source
CFRunLoopSourceRef
是事件产生的地方。有两个版本 Source0
和 Source1
:
-
source0
: 手动唤醒线程。 触摸事件,performSelector:onThread:withObject:
方法。 -
source1
:自动唤醒线程,基于Port的线程间通讯 ,系统事件的捕捉。
Timer
CFRunLoopTimerRef
基于时间的触发器,可以与NSTimer进行转换。 performSelector:withObject:afterDelay:
Observer
CFRunLoopObserverRef
是观察者,每个 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 };
上面的 Source/Timer/Observer
被统称为 mode item
,一个 item
可以被同时加入多个 mode
。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry: NSLog(@"KCFRunLoopEntry"); break; case kCFRunLoopBeforeTimers: NSLog(@"Timers"); break; case kCFRunLoopBeforeSources: NSLog(@"Sources"); break; case kCFRunLoopBeforeWaiting: NSLog(@"BeforeWaiting"); break; case kCFRunLoopAfterWaiting: NSLog(@"AfterWaiting"); break; case kCFRunLoopExit: NSLog(@"Exit"); break; default: break; } }); CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes); CFRelease(observer);
RunLoop运行逻辑
RunLoop的应用
1. 监控线程生命周期 2. 解决NSTimer在滑动停止工作的问题 3. 监控应用卡顿 4. 性能优化
滑动TableView的时候定时器为什么会失效?
因为滑动TableView的时候,RunLoop发生了Mode切换。
kCFRunLoopDefaultMode
—> UITrackingRunLoopMode
。
解决方法:将Timer添加到RunLoop使用 commonMode
标记设置。
// 这种方式添加定时器不会出现,Timer停止的问题。 static int count = 0; NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"%d", count ++); }]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; // NSRunLoopCommonModes 并不是一个真的模式,而是一个标记。
如何实现常驻线程?
经常使用子线程时,可以创建一个常驻子线程,可以节省系统资源。下面看创建常驻子线程:
BOOL shouldKeepRunning = YES; // global - (void)viewDidLoad { [super viewDidLoad]; // LEThread继承自NSThread重写dealloc方法,打印销毁信息。 self.thread = [[LEThread alloc] initWithBlock:^{ // 用于线程保活,不做具体的任务。 // 线程一直在运行 [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; // [[NSRunLoop currentRunLoop] run]; // 开启一个无限的循环,无法停止。 NSRunLoop *theRL = [NSRunLoop currentRunLoop]; while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); NSLog(@"%s ---end----", __func__); }]; // 启动子线程 [self.thread start]; } //停止线程 // 停止子线程RunLoop,该方法需要在子线程中调用。 - (void)stop { shouldKeepRunning = NO; // 标志 CFRunLoopStop(CFRunLoopGetCurrent()); // 释放当前RunLoop。 NSLog(@"%s", __func__); }
- 为当前线程开启一个RunLoop。
- 向该RunLoop中添加一个Port/Source等维持RunLoop的事件循环。
- 启动该RunLoop。
实例代码: GitHub
小结
RunLoop
本文并没有深入研究RunLoop,只是梳理RunLoop相关的知识点。如果想深入学习推荐《小马哥底层课程》。
GNUstep参考
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- ABP开发框架前后端开发系列---(14)基于Winform的ABP快速开发框架
- Java开发人员的Flex开发
- Java开发人员的Flex开发
- 行为驱动开发让开发做正确事
- 让开发者专注于应用开发,OpenCenter 3.0 开发者预览版发布
- 让开发者专注于应用开发,OpenCenter 3.0 开发者预览版发布
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Introduction to Programming in Java
Robert Sedgewick、Kevin Wayne / Addison-Wesley / 2007-7-27 / USD 89.00
By emphasizing the application of computer programming not only in success stories in the software industry but also in familiar scenarios in physical and biological science, engineering, and appli......一起来看看 《Introduction to Programming in Java》 这本书的介绍吧!