Runloop

Runloop是面试中的重点,也是开发过程中对性能优化的点

简介

什么是RunLoop

  • 运行循环
  • 在程序运行过程中循环做一些事情

应用范畴

  • 定时器(Timer)、PerformSelector
  • GCD Async Main Queue
  • 事件响应、手势识别、界面刷新
  • 网络请求
  • AutoreleasePool

作用

如果没有RunLoop

1
2
3
4
int main(int argc, const char * argv[]) {
NSLog(@"Hello World!");
return 0;
}

执行完第3行代码后,会即将退出程序

如果有了RunLoop

1
2
3
4
5
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

程序并不会马上退出,而是保持运行状态

RunLoop的基本作用

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

RunLoop对象

iOS中有2套API来访问和使用RunLoop

  • Foundation - NSRunLoop
  • Core Foundation - CFRunLoopRef

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

如何获取Runloop对象

  • Foundation

    • [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
    • [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
  • Core Foundation

    • CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
    • CFRunLoopGetMain(); // 获得主线程的RunLoop对象

RunLoop的5个关键类

类名 描述
CFRunLoopRef Runloop对象
CFRunLoopModeRef RunLoop的运行模式
CFRunLoopSourceRef 系统事件,触摸事件,基于Port的线程间通信等
CFRunLoopTimerRef 定时器
CFRunLoopObserverRef 监听器状态

类关系

属性 描述
Source0 触摸事件处理
performSelector:onThread:
Source1 基于Port的线程间通信
系统事件捕捉
Timers NSTimer
performSelector:withObject:afterDelay:
Observers 用于监听RunLoop的状态
UI刷新(BeforeWaiting)
Autorelease pool(BeforeWaiting)

CFRunLoopRef

列举CFRunloop对象的关键属性

1
2
3
4
5
6
7
8
typedef struct __CFRunLoop *CFRunLoopRef;
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
};
属性 类型 描述
_pthread 线程对象 与之对应的线程
_commonModes 集合
_commonModeItems 集合
_currentMode CFRunLoopModeRef 当前运行的模式
_modes 集合<CFRunLoopModeRef> 所有模式

一个RunLoop包含若干个Mode
RunLoop启动时只能选择其中一个Mode,作为_currentMode

CFRunLoopModeRef

列举CFRunLoopModeRef对象的关键属性

1
2
3
4
5
6
7
8
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
属性 类型 描述
_name str 模式名称
_sources0 集合<CFRunLoopSourceRef>
_sources1 集合<CFRunLoopSourceRef>
_observers Array<CFRunLoopObserversRef>
_timers Array<CFRunLoopTimerRef>

模式种类

CoreFoundation Foundation 描述
kCFRunLoopDefaultMode NSDefaultRunLoopMode App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode UITrackingRunLoopMode 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
UIInitializationRunLoopMode - 在刚启动 App 时进入的第一个 Mode,启动完成后就不再使用
GSEventReceiveRunLoopMode - 接受系统事件的内部 Mode,通常用不到
kCFRunLoopCommonModes NSRunLoopCommonModes 这是一个占位用的Mode,不是一种真正的Mode

每个Mode又包含若干个Source0/Source1/Timer/Observer
不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出

CFRunLoopObserverRef

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
};

应用场景

应用一(解决NSTimer在滑动时停止工作的问题)

Q:为什么Timer在页面滚动之后会失效?如何解决
A:默认创建Timer是在kCFRunLoopCommonModes模式下,需要我们把Timer加入到kCFRunLoopCommonModes下就可以了;

1
2
3
4
5
6
//创建timer
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer *timer) {
//需要执行的代码
}];
// 把timer手动添加到自己新建的Runloop中,添加模式为通用的占位模式
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

应用二(性能优化)

Q:在主线程中做复杂运算
A:监听主线程Runloop的运行状态,在Runloop空闲的时候做操作

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
CFRunLoopObserverRef observerRef = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即将进入Loop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理timer事件");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理sources事件");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"刚从休眠中唤醒");
break;
case kCFRunLoopExit:
NSLog(@"即将推出Loop");
break;
default:
break;
}
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observerRef, kCFRunLoopDefaultMode);
// 下面代码在适当时刻销毁
CFRelease(observerRef)

应用三(控制线程生命周期-线程保活)

Q:当子线程中的任务执行完毕之后就被销毁了;那么如果我们需要开启一个子线程,在程序运行过程中永远都存在,那么我们就会面临一个问题,如何让子线程永远活着?
A:常驻线程:给子线程开启一个RunLoop
A:AFNetworking 线程保活,防止线程频繁创建销毁浪费性能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// RCPermenantThread.h
#import <Foundation/Foundation.h>

typedef void(^RCPermenantThreadBlock)(void);
@interface RCPermenantThread : NSObject

/// 执行任务
/// @param target 目标对象
/// @param action 方法
/// @param object 附件
- (void)executeTaskWithTarget:(id)target
action:(SEL)action
object:(id)object;

/// 执行任务
/// @param taskBlock 任务代码块
- (void)executeBlock:(RCPermenantThreadBlock)taskBlock;

/// 结束
- (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
// RCPermenantThread.m
#import "RCPermenantThread.h"

@interface RCPermenantThread ()
@property (nonatomic, strong) NSThread *innerThread;
@property (nonatomic, assign) BOOL stopped;
@end

@implementation RCPermenantThread

- (void)dealloc {
[self performSelector:@selector(__stop) onThread:_innerThread withObject:nil waitUntilDone:YES];
}

- (instancetype)init {
self = [super init];
if (self) {
_stopped = NO;
__weak typeof(self)weakSelf = self;
_innerThread = [[NSThread alloc] initWithBlock:^{
// 线程保活
NSLog(@"%@", [NSThread currentThread]);
// 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
// 为当前默认模型增加Port(Source1)
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
// 如果要主动推出Runloop,就不可以直接只用run方法,否则runloop结束不掉
while (weakSelf && !weakSelf.stopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
//[[NSRunLoop currentRunLoop] run];
}
NSLog(@"RCPermenantThread Runloop over");
}];
[_innerThread start];
}
return self;
}

#pragma mark - Public

- (void)stop {
if (!_innerThread || _stopped) { return; }
[self performSelector:@selector(__stop) onThread:_innerThread withObject:nil waitUntilDone:YES];
}

- (void)executeTaskWithTarget:(id)target action:(SEL)action object:(id)object {
if (!_innerThread || !target || !action) { return; }
[target performSelector:action onThread:_innerThread withObject:object waitUntilDone:NO];
}

- (void)executeBlock:(RCPermenantThreadBlock)taskBlock {
if (!_innerThread || !taskBlock) { return; }
[self performSelector:@selector(__executeTask:) onThread:_innerThread withObject:taskBlock waitUntilDone:NO];
}

#pragma mark - Private

// 停止子线程的Runloop;如果不停止Runloop, 线程会销毁不了.
- (void)__stop {
_stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
_innerThread = nil;
}

- (void)__executeTask:(RCPermenantThreadBlock)taskBlock {
if (taskBlock) {
taskBlock();
}
}

@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
// ViewController.m
#import "ViewController.h"
#import "RCPermenantThread.h"
@interface ViewController ()

@property (nonatomic, strong) RCPermenantThread *thread;

@end
@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
_thread = [[RCPermenantThread alloc] init];
}

// 为子线程增加任务
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.thread executeBlock:^{
NSLog(@"执行任务:%s", __func__);
}];
}

@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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
@interface MCLagMonitor() {
int timeoutCount;
CFRunLoopObserverRef runLoopObserver;
@public
dispatch_semaphore_t dispatchSemaphore;
CFRunLoopActivity runLoopActivity;
}

@property (nonatomic, strong) NSTimer *cpuMonitorTimer;

@end

@implementation MCLagMonitor

+ (instancetype)shareInstance {
static MCLagMonitor *monitor = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
monitor = [[MCLagMonitor alloc] init];
});
return monitor;
}

- (void)startMonitor {
if (runLoopObserver) {
return;
}
dispatchSemaphore = dispatch_semaphore_create(0); //Dispatch Semaphore保证同步
//创建一个观察者
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context);
//将观察者添加到主线程runloop的common模式下的观察中
CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);

//创建子线程监控
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//子线程开启一个持续的loop用来进行监控

while (YES) {

long semaphoreWait = dispatch_semaphore_wait(self->dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));

// semaphoreWait 的值不为 0, 说明线程被堵塞

if (semaphoreWait != 0) {

if (!self->runLoopObserver) {

self->timeoutCount = 0;

self->dispatchSemaphore = 0;

self->runLoopActivity = 0;

return;

}

// BeforeSources和 AfterWaiting 这两个 runloop 状态的区间时间能够检测到是否卡顿

if (self->runLoopActivity == kCFRunLoopBeforeSources || self->runLoopActivity == kCFRunLoopAfterWaiting) {

// 将堆栈信息上报服务器的代码放到这里

if (++ self->timeoutCount < 5) { //连续5次就是250毫秒

continue;

} else {

NSLog(@"卡顿了");

}

} //end activity

}// end semaphore wait

self->timeoutCount = 0;

}// end while

});
}

- (void)stopMonitor {
[self.cpuMonitorTimer invalidate];
if (!runLoopObserver) {
return;
}

CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);

CFRelease(runLoopObserver);
runLoopObserver = NULL;
}

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
MCLagMonitor *lagMonitor = (__bridge MCLagMonitor*)info;
lagMonitor->runLoopActivity = activity;
dispatch_semaphore_t semaphore = lagMonitor->dispatchSemaphore;
dispatch_semaphore_signal(semaphore);
}