RunLoop

以下是 RunLoop 相关的内容,前半部分是理论知识,后半部分是代码。。。

一、RunLoop 基础知识

1.1 RunLoop 简介

一般来说,一个线程只能执行一个任务,执行完就会退出,如果我们需要一种机制,让线程能随时处理时间但并不退出,那么 RunLoop 就是这样的一个机制。Runloop是事件接收和分发机制的一个实现。所以它实际上是一个对象,这个对象管理了其需要处理的事件和消息。在iOS 系统中,有这样两个对象: NSRunLoop 和 CFRunLoopRef。 NSRunLoop 和 CFRunLoopRef都代表着 RunLoop 对象。

1.2 RunLoop的基本作用

(1)保持程序的持续运行; (2)处理 App 中的各种事件(比如触摸事件、定时器事件、Selector 事件); (3)节省 CPU 资源, 提高程序性能。

1.3 RunLoop的使用环境

仅当在为你的程序创建辅助线程的时候,你才需要显示运行 RunLoop 。对于辅助线程,你需要判断一个 RunLoop 是否是必须的。如果是必须的,那么你要自己配置并启动它,你不需要再任何情况下都去启动一个线程的 RunLoop 。 RunLoop 在你要和线程有更多的交互时才需要,比如以下情况: (1)使用端口或者自定义输入源来和其他线程通信; (2)使用线程的定时器; (3) Cocoa 中使用任何 performSelector 的方法; (4)使线程周期性工作。

1.4 RunLoop工作的特点:

(1)当有时间发生时, RunLoop 会根据具体的事件类型通知应用程序作出响应; (2)当没有事件发生时, RunLoop 会进入休眠状态; (3)当事件再次发生时, RunLoop 会被重新唤醒, 处理事件。

1.5 runLoop的内部逻辑

runLoop其实是一个函数,其内部是一个 do-while 循环,当你调用CFRunLoop() 时,线程就会一直停留在这个循环中;直到超时或被手动停止,该函数才会返回。每次运行 runLoop,线程的 runLoop 会自动处理之前未处理的消息,并通知相关的观察者,具体的顺序如下: (1)通知观察者 runLoop 已经启动; (2)通知观察者处理将要开始的定时器; (3)通知观察者处理即将启动的非基于端口的源; (4)处理非基于端口的源; (5)如果基于端口的源准备好并处于等待状态,立即启动进入步骤⑨; (6)通知观察者线程即将进入休眠; (7)线程处于休眠状态,直到下面的任意的一个事件发生: A.某一时间到达基于端口的源; B.定时器启动; C.runLoop被外部手动唤醒; (8)通知观察者线程刚被唤醒; (9)处理接收到的事件,处理定时器并重启 runLoop,进入步骤2; (10)通知观察者,即将退出 runLoop。

1.6 RunLoop与线程的关系

(1)每条线程都有唯一的一个与之对应的 RunLoop 对象; (2)主线程的 RunLoop 已经自动创建好了, 子线程的 RunLoop 需要手动创建; (3) RunLoop在第一次获取时创建, 在线程结束时销毁; (4)直线线程与圆形线程:直线线程执行的任务是一条直线;而圆形线程不断循环,直到通过某种方式截止,在 iOS 中,圆形线程就是通过 runLoop 实现的。

1.7 RunLoop对象

(1) iOS中有2套 API 来访问和使用 RunLoop。 Foundation框架 : NSRunLoop; Core Foundation框架: CFRunLoopRef。

(2) NSRunLoop 和 CFRunLoopRef 都代表着 RunLoop 对象 ,它们是等价的,可以互相转换;

(3) NSRunLoop 是基于 CFRunLoopRef 的一层 OC 包装,所以要了解 RunLoop 内部结构,需要多研究 CFRunLoopRef 层面的 API(Core Foundation层面)。

1.8获得 RunLoop 对象

苹果不允许直接创建RunLoop,它只提供了两个自动获取的函数。 (1) Foundation [NSRunLoop currentRunLoop]; //获得当前线程的 RunLoop 对象 [NSRunLoop mainRunLoop]; //获得主线程的 RunLoop 对象

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

1.9 相关类:

Core Foundation中关于 RunLoop 的 5 个类 ,RunLoop 如果没有这些东西, 会直接退出。 (1) CFRunLoopRef (2) CFRunLoopModeRef 该类并没有对外暴露; (3) CFRunLoopSourceRef (4) CFRunLoopTimerRef (5) CFRunLoopObserverRef

1.10 CFRunLoopSourceRef 事件源(输入源)

时间产生的地方,source 有两个版本: (1) source0:只包含一个回调,它并不能主动触发事件,使用时,需先调用 CFRunLoopSourceSignal 将这个 source 标记为待处理,后调用 CFRunLoopWakeUp 来唤醒 runLoop,让其处理这个事件; (2) source1:包含一个 mach_port 和一个回调,被用于通过内核和其他线程互发送消息,这种 source 能主动唤醒 runLoop 线程。

1.11 CFRunLoopObserverRef

CFRunLoopObserverRef是观察者,能够监听 RunLoop 的状态改变,当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:

kCFRunLoopEntry = (1UL « 0), // 即将进入 Loop

kCFRunLoopBeforeTimers = (1UL « 1), //即将处理 Timer

kCFRunLoopBeforeSources = (1UL « 2), //即将处理 Source

kCFRunLoopBeforeWaiting = (1UL « 5), //即将进入休眠

kCFRunLoopAfterWaiting = (1UL « 6), //刚从休眠中唤醒

kCFRunLoopExit = (1UL « 7), //即将退出 Loop

1.12 runLoop的模式

runLoop 中使用 mode 来指定时间在运行循环中的优先级,系统默认注册了 5 个Mode: (1)NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认Mode,通常主线程是在这个Mode下运行;

(2) UITrackingRunLoopMode:界面跟踪Mode,用于 ScrollView 追踪触摸滑动, 保证界面滑动时不受其他 Mode 影响;

(3) UIInitializationRunLoopMode:启动程序后的过渡 mode,启动完成后就不再使用;

(4) GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到;

(5)kCFRunLoopCommonModes(NSRunLoopCommonModes): 这是一个占位用的 Mode ,作为标记 DefaultMode 和CommonMode 用。

二、演示代码

演示代码一:常驻线程

控制器.m中的代码:

 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
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[NNThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[self.thread start];
}

#pragma mark -常驻线程方式一
- (void)run {
@autoreleasepool {

[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
NSLog(@"-- run -- %@ --", [NSThread currentThread]);
[[NSRunLoop currentRunLoop] run];
NSLog(@"--不会执行--"); // 不会执行
}
}

#pragma mark -常驻线程方式二
- (void)run1{
@autoreleasepool {
while (1) {
[[NSRunLoop currentRunLoop] run];
NSLog(@"-- run1 -- %@ --", [NSThread currentThread]);
}
}
}

#pragma mark -常驻线程方式三
- (void)run2{
@autoreleasepool {
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
NSLog(@"-- run2 -- %@ --", [NSThread currentThread]);
[[NSRunLoop currentRunLoop] run];
}
}

#pragma mark -在该线程中自定义事件
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

- (void)test {
NSLog(@"-- test -- %@ --", [NSThread currentThread]);
}

线程子类化:

1
2
3
4
5
6
7
8
9

#import "NNThread.h"
@implementation NNThread

#pragma mark -重写了 dealloc 方法,查看线程是否销毁
- (void)dealloc {
NSLog(@"线程 NNThread 被销毁"); // 不会打印
}
@end

演示代码二: GCD 定时器:

 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
#pragma mark - GCD 的 timer
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//获得队列
dispatch_queue_t queue = dispatch_get_main_queue();
//创建一个定时器 (dispatch_source_t 本质还是个 OC 对象)

self.timer =dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// GCD的时间参数,一般是纳秒(1秒== 10的9次方纳秒)
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
dispatch_source_set_timer(self.timer, start, interval, 0);

//设置回调
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"------ %@ ------", [NSThread currentThread]);
count++;
if (count == 10) {
//取消定时器
dispatch_cancel(self.timer);
self.timer = nil;
}
});

//启动定时器
dispatch_resume(self.timer);
}

演示代码三:观察者

 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
#pragma mark -观察者
-(void)observerTest{
/**
CFRunLoopObserverRef是观察者, 能够监听 RunLoop 的状态改变,当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。

      CFRunLoopObserverRef参数:
第一个参数:分配存储空间
第二个参数:监听状态
第三个参数:是否要持续监听
第四个参数:优先级
第五个参数:回调
*/

//创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即将进入runloop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop即将处理 timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"RunLoop即将处理Sources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"RunLoop即将进入休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"RunLoop刚从休眠中唤醒");
break;
case kCFRunLoopExit:
NSLog(@"即将退出RunLoop ");
break;
default:
break;
}
});
/**
RunLoop的模式
1.NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认Mode,通常主线程是在这个Mode下运行
2.UITrackingRunLoopMode:界面跟踪 Mode, 用于 ScrollView 追踪触摸滑动, 保证界面滑动时不受其他 Mode 影响
3.UIInitializationRunLoopMode:启动程序后的过渡 mode,启动完成后就不再使用
4.GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到
5.kCFRunLoopCommonModes(NSRunLoopCommonModes): 这是一个占位用的 Mode, 作为标记 DefaultMode 和 CommonMode 用
*/

/**
CFRunLoopAddObserver参数:
第一个参数:要监听哪个RunLoop
第二个参数:监听者
第三个参数:要监听RunLoop在哪种运行模式下的状态
*/

// 添加观察者:监听RunLoop的状态
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

// 释放  Observer
CFRelease(observer);
}

- (void)timerTest
{
//调用了 scheduledTimer 返回的定时器,已经自动被添加到当前 RunLoop 中,默认是 NSDefaultRunLoopMode
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

//修改模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

end


结束语:RunLoop 的总结就到此为止了,以后如果想到或遇到新知识点,会再来补充!另外如果文章写的有什么错误,烦请各位能指出来!谢谢大家!