RunLoop 学习笔记

什么是RunLoop

顾名思义,运行循环,在程序运行过程中循环做一些事情。

应用范畴

  • 定时器(Timer)、PerformSelector
  • GCD Async Main Queue
  • 事件响应、手势识别、界面刷新
  • 网络请求
  • AutoreleasePool
1
2
3
4
5
6
int main(int argc, char * argv[]) {
@autoreleasepool {
// 开启Runloop循环,程序并不会马上退出,而是保持运行状态
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

RunLoop的基本作用

  • 保持程序的持续运行
  • 处理App中的各种事件(比如触摸事件、定时器事件等)
  • 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
  • ……

RunLoop对象

有2套API来访问和使用RunLoop

  • Foundation:NSRunLoop
  • Core Foundation:CFRunLoopRef
1
2
3
// Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
1
2
3
// Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

NSRunLoop和CFRunLoopRef都代表着RunLoop对象,NSRunLoop是基于CFRunLoopRef的一层OC包装CFRunLoopRef是开源的

RunLoop与线程

每条线程都有唯一的一个与之对应的RunLoop对象,RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value。

1
2
// 伪代码
runloops[thread] = runloop

线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建,在线程结束时销毁。

主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop。

RunLoop相关的类

Core Foundation中关于RunLoop的5个类

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef
1
2
3
4
5
6
7
8
// RunLoop 结构体
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
};
1
2
3
4
5
6
7
8
// RunLoopMode 结构体
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};

由结构体可见,CFRunLoopRef_modes成员变量中存着多个CFRunLoopModeRefCFRunLoopMode存着CFRunLoopSourceRefCFRunLoopTimerRefCFRunLoopObserverRef

CFRunLoopModeRef

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 影响

CFRunLoopObserverRef

_observers 是用来监听 RunLoop 事件。

1
2
3
4
5
6
7
8
9
10
/* Run Loop Observer Activities */
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
kCFRunLoopAllActivities = 0x0FFFFFFFU
};

我们可以通过添加Observer来监听Runloop事件

1
2
3
4
5
6
7
8
// 创建 Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
// 监听各类 RunLoop 事件
});
// 添加 Observer 到 RunLoop 中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);

CFRunLoopSourceRef

Source0

  • 触摸事件处理
  • performSelector:onThread:

Source1

  • 基于Port的线程间通信
  • 系统事件捕捉

CFRunLoopTimerRef

Timers

  • NSTimer
  • performSelector:withObject:afterDelay:

RunLoop的运行逻辑

  • 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

RunLoop的应用

解决NSTimer在滑动时停止工作的问题

1
2
3
4
5
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:nil];
// NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式
// NSRunLoopCommonModes并不是一个真的模式,它只是一个标记
// timer能在_commonModes数组中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

控制线程生命周期(线程保活)

1
2
3
4
5
6
7
8
9
10
11
12
typedef void (^MJPermenantThreadTask)(void);
@interface MJPermenantThread : NSObject
/** 开启线程 */
- (void)run;
/** 在当前子线程执行一个任务 */
- (void)executeTask:(MJPermenantThreadTask)task;
/** 结束线程 */
- (void)stop;
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/** MJThread **/
@interface MJThread : NSThread
@end
@implementation MJThread
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
/** MJPermenantThread **/
@interface MJPermenantThread()
@property (strong, nonatomic) MJThread *innerThread;
@property (assign, nonatomic, getter=isStopped) BOOL stopped;
@end
@implementation MJPermenantThread
#pragma mark - public methods
- (instancetype)init {
if (self = [super init]) {
self.stopped = NO;
__weak typeof(self) weakSelf = self;
// 初始化线程
self.innerThread = [[MJThread alloc] initWithBlock:^{
// 第一次获取时就会创建 RunLoop 对象
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
// 判断是否需要继续循环
while (weakSelf && !weakSelf.isStopped) {
// Runloop 运行模式
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
// 启动线程
[self.innerThread start];
}
return self;
}
- (void)run {
if (!self.innerThread) return;
[self.innerThread start];
}
- (void)executeTask:(MJPermenantThreadTask)task {
if (!self.innerThread || !task) return;
// 在子线程执行任务
[self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
- (void)stop {
if (!self.innerThread) return;
// 在子线程退出 Runloop 循环
[self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)dealloc {
NSLog(@"%s", __func__);
// 对象销毁前退出 Runloop
[self stop];
}
#pragma mark - private methods
- (void)__stop {
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
// 将block执行封装在方法中
- (void)__executeTask:(MJPermenantThreadTask)task {
task();
}

监控应用卡顿

待续

性能优化

待续