首页 > 代码库 > iOS核心笔记——RunLoop-基础
iOS核心笔记——RunLoop-基础
1、RunLoop介绍:
?了解
:RunLoop从字面意思看就是运行循环
、跑圈
,通常情况下,一个线程一次只能执行一个任务;任务执行完毕后线程就会进入消亡状态随之退出。有时候我们希望线程执行完任务之后还能随时处理事件且不退出,所以,iOS提供了RunLoop。
1-1、什么是RunLoop?
?重要
:RunLoop实际上就是一个对象,RunLoop对象管理其需要处理的事件和消息;RunLoop能够让线程在没有处理消息时进入休眠状态以避免占用资源、在监听到事件源发送的消息时立刻唤醒线程。RunLoop提供了一个入口函数来实现运行循环
,当线程执行该函数后,便会处于接收消息
->等待处理
->处理
的循环中,直到运行循环结束、RunLoop退出;RunLoop对象也随之销毁。
RunLoop循环逻辑:
?重要
:在iOS程序中,mian函数
中的UIApplicationMain()函数
内部启动了一个与主线程相关联的RunLoop;所以,UIApplicationMain()函数一直没有返回;保持程序持续运行。
RunLoop实现原理:
1-2、RunLoop对象:
?了解
:苹果不允许我们直接通过alloc、init方式创建线程中的RunLoop对象,只有在第一次访问线程中的RunLoop对象时,RunLoop对象将以懒加载的形式创建
,所以,iOS中提供了2套API来访问和使用RunLoop。
1、Foundation框架:
- NSRunLoop:在Foundation框架中,一个NSRunLoop对象就代表了一个线程中的运行循环。
- 获取方式:
类型 | 方式 | 备注 |
---|---|---|
当前线程 | 通过NSRunLoop的currentRunLoop类方法 便能获取到当前线程 中的RunLoop对象 |
当前RunLoop对象可以是主线程中的RunLoop对象,也可以是子线程中的RunLoop对象,如果当前为子线程,则懒加载RunLoop对象之后需要手动调用run 方法开启运行循环 |
主线程 | 通过NSRunLoop的mainRunLoop类方法 能够获取到主线程 中的RunLoop对象 |
当当前线程为主线程时,mainRunLoop与currentRunLoop获取到的是同一个对象 |
2、Core Foundation框架:
- CFRunLoopRef:Core Foundation框架中,一个CFRunLoopRef对象就代表线程中的运行循环。
- 获取方式:
类型 | 方式 | 备注 |
---|---|---|
主线程 | CFRunLoopGetMain()函数 | 获取主线程中的RunLoop对象,程序运行时,自动创建并开启 |
当前线程 | CFRunLoopGetCurrent()函数 | 获取当前线程中的RunLoop对象,如果当前为子线程,则懒加载RunLoop对象之后需要手动调用run 方法开启运行循环 |
1-3、RunLoop与线程:
1. /// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
2. static CFMutableDictionaryRef loopsDic;
3. /// 访问 loopsDic 时的锁
4. static CFSpinLock_t loopsLock;
5.
6. /// 获取一个 pthread 对应的 RunLoop。
7. CFRunLoopRef_CFRunLoopGet(pthread_t thread){
8. OSSpinLock Lock(&loopsLock);
9.
10. if(!loopsDic){
11. // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
12. loopsDic = CFDictionaryCreateMutable();
13. CFRunLoopRef mainLoop = _CFRunLoopCreate();
14. CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
15. }
16.
17. /// 直接从 Dictionary 里获取。
18. CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread);
19.
20. if(!loop){
21. /// 取不到时,创建一个
22. loop = _CFRunLoopCreate();
23. CFDictionarySetValue(loopsDic, thread, loop);
24. /// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
25. _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
26. }
27.
28. OSSpinLockUnLock(&loopsLock);
29. returnloop;
30. }
31.
32. CFRunLoopRef CFRunLoopGetMain(){
33. return _CFRunLoopGet(pthread_main_thread_np());
34. }
35.
36. CFRunLoopRef CFRunLoopGetCurrent(){
37. return _CFRunLoopGet(pthread_self());
38. }
?重要
: CFRunLoop是基于pthread来管理的,线程和RunLoop之间是一一对应的,其关系是保存在一个全局的Dictionary里。线程刚创建时并没有RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。只能在一个线程的内部获取其 RunLoop(主线程除外)。
1-4、主线程与子线程:
?重要
:在子线程中使用RunLoop时,最好在子线程中的任务中包一个自动释放池(即:在子线程中的任务中手动添加@autoreleasepool
,将任务包裹起来,避免内存泄露)。
1-5、RunLoop注意事项:
?了解
:1、每条线程都有唯一一个与之对应的RunLoop对象;
?了解
:2、主线程的RunLoop对象在程序启动时已经自动创建完毕,并自动开启运行循环;子线程的RunLoop对象需要主动访问其RunLoop对象以懒加载的形式创建,并且,子线程的RunLoop对象需要调用start
方法手动开启;
?了解
:3、RunLoop在第一次获取时以懒加载的形式创建,在线程结束时销毁。
?了解
:4、RunLoop对象必须依赖线程,即:有线程才有RunLoop对象,没有线程就没有RunLoop对象;不能有RunLoop对象而没有线程。
1-6、RunLoop学习:
1、苹果官方文档:
RunLoop官方文档介绍
2、CFRunLoopRef源码:
CFRunLoop源码
2、RunLoop对外接口:
?重要
:在 CoreFoundation 里面关于 RunLoop 有5个类:
CFRunLoopRef
:RunLoop对象
CFRunLoopModeRef
:RunLoop模式
CFRunLoopSourceRef
:RunLoop事件源
CFRunLoopTimerRef
:RunLoop定时器
CFRunLoopObserverRef
:RunLoop观察者其中,CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。它们之间的关系如下图所示:
2-1、CFRunLoopRef:
?重要
:1、一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
?重要
:2、RunLoop中的Source/Timer/Observer被统称为mode item
,一个 item 可以被同时加入多个mode。但一个 item被重复加入同一个mode时是不会有效果的。如果一个 mode 中一个item都没有,则RunLoop会直接退出,不进入循环。
?了解
:3、
2-2、CFRunLoopSourceRef:
?重要
:CFRunLoopSourceRef是事件产生的地方,Source有两个版本:Source0 和 Source1。
· Source0
:非基于Post,只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
· Source1
:基于Port,包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。
?了解
:以前CFRunLoopSourceRef可分为:①Port-Based Sources
;②Custom Input Sources
;③Cocoa Perform Selector Sources
。
基于事件源的函数调用栈:
2-3、CFRunLoopTimerRef:
?重要
:CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer是toll-free bridged的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
2-4、CFRunLoopObserverRef:
?重要
:CFRunLoopObserverRef是观察者,每个Observer都包含了一个回调(函数指针),当RunLoop的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:1.typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
2. kCFRunLoopEntry = (1UL << 0), // 1 --- 即将进入RunLoop
3. kCFRunLoopBeforeTimers = (1UL << 1), // 2 --- 即将处理定时器
4. kCFRunLoopBeforeSources = (1UL << 2), // 4 --- 即将处理Source
5. kCFRunLoopBeforeWaiting = (1UL << 5), // 32 --- 即将进入休眠状态
6. kCFRunLoopAfterWaiting = (1UL << 6), // 64 --- 唤醒RunLoop
7. kCFRunLoopExit = (1UL << 7), // 128 --- 即将退出RunLoop
8. kCFRunLoopAllActivities = 0x0FFFFFFFU // RunLoop所有状态
9.};
?重要
:给RunLoop对象设置观察者:
步骤
:①创建观察者对象;②将观察者对象设置为指定RunLoop对象的观察者。
3、CFRunLoopModeRef:
?重要
:CFRunLoopModeRef代表RunLoop的运行模式,一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
3-1、CFRunLoopMode和CFRunLoop的结构大致如下:
CFRunLoopMode结构:
1.struct__CFRunLoopMode{
2. CFStringRef_name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
3. CFMutableSetRef_sources0; // Set<CFRunLoopSourceRef>
4. CFMutableSetRef_sources1; // Set<CFRunLoopSourceRef>
5. CFMutableArrayRef_observers;// Array<CFRunLoopObserverRef>
6. CFMutableArrayRef_timers; // Array<CFRunLoopTimerRef>
7. ...
8.};
CFRunLoop结构:
1.struct__CFRunLoop{
2. CFMutableSetRef_commonModes; // Set<CFStringRef>
3. CFMutableSetRef_commonModeItems; // Set<Source/Observer/Timer>
4. CFRunLoopModeRef_currentMode; // Current Runloop Mode
5. CFMutableSetRef_modes; // Set<CFRunLoopModeRef>
6. ...
7.};
3-2、RunLoop5种模式:
?重要
: “CommonModes”:一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。
?了解
:应用场景举例:主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都已经被标记为”Common”属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。有时你需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 “commonModeItems” 中。”commonModeItems” 被 RunLoop 自动更新到所有具有”Common”属性的 Mode 里去。
CFRunLoop对外暴露的管理 Mode 接口只有下面2个:1. CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
2. CFRunLoopRunInMode(CFStringRef modeName, ...);
3.
Mode 暴露的管理 mode item 的接口有下面几个:
1. CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
2.
3. CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
4.
5. CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
6.
7. CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
8.
9. CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
10.
11. CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
?重要
:只能通过mode name
来操作内部的mode,当传入一个新的mode name
时,但是,RunLoop内部没有对应mode时,RunLoop会自动帮你创建对应的 CFRunLoopModeRef。对于一个RunLoop来说,其内部的mode只能增加不能删除。苹果公开提供的 Mode 有两个:kCFRunLoopDefaultMode (NSDefaultRunLoopMode)
和UITrackingRunLoopMode
,可以用这两个Mode Name来操作其对应的Mode。同时苹果还提供了一个操作 Common 标记的字符串:kCFRunLoopCommonModes (NSRunLoopCommonModes),可以用这个字符串来操作 Common Items,或标记一个 Mode 为 “Common”。使用时注意区分这个字符串和其他 mode name。
3-3、RunLoop模式详细介绍:
iOS核心笔记——RunLoop-基础