前言-什么是RunLoop?
什么是RunLoop
? 跑圈?字面上理解确实是这样的。
Apple官方文档这样解释RunLoop
RunLoop是与线程息息相关的基本结构的一部分。RunLoop是一个调度任务和处理任务的事件循环。RunLoop的目的是为了在有工作的时候让线程忙起来,而在没有工作的时候让线程进入休眠状态。
之所以iOS的app能够持续的响应从而让程序保持运行状态,在于其存在一个事件循环(Event Loop
)机制: 线程能够随时响应并处理事件的机制,这种机制要求线程不能退出从而高效的完成事件调度和处理。
在iOS这种事件循环机制就叫做RunLoop
RunLoop
实际上是一个对象,对象在循环中处理程序运行过程出现的各种事件(比如触摸事件,UI刷新事件,定时器事件,Selector
事件)从而保持程序的持续运行并且让程序在没有事件处理的时候进入休眠状态,从而节省CPU资源达到提升程序性能的目的。
默认情况下主线程的RunLoop原理
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
这是 iOS 应用程序的主运行循环,它负责处理用户事件、界面更新和应用程序的主要逻辑。UIApplicationMain
函数创建应用程序对象和主运行循环,并传递控制权给应用程序的委托类(AppDelegate
)来处理应用程序的逻辑。
其中的UIApplicationMain
函数内部帮我们开启了主线程的RunLoop
。
UIApplicationMain
内部拥有一个无限循环的代码。
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
程序会一直在do-while
循环中执行。
Apple官方的RunLoop
模型图
RunLoop
就是线程中的一个循环,RunLoop
在循环中不断检测,通过Input sources
(输入源)和Timer sources
(定时源)两种来源等待接受消息,然后对接收到的事件通知线程进行处理,并在没有事件的时候进行休息。
1. RunLoop对象
RunLoop
对象是基于CFFoundation框
架的CFRunLoopRef
类型封装的对象。
NSRunLoop
是基于CFRunLoopRef
的封装,提供了面向对象的API,但是这些API不是线程安全的。
[NSRunLoop currentRunLoop];//获得当前RunLoop对象
[NSRunLoop mainRunLoop];//获得主线程的RunLoop对象
CoreFoundation
框架的 CFRunLoopRef
对象
CFRunLoopRef
是在CoreFoundation
框架内的,其提供了纯C语言函数的API,所有这些API都是线程安全的。
CFRunLoopGetCurrent();//获得当前线程的RunLoop对象
CFRunLoopGetMain();//获得主线程的RunLoop对象
那么对应的两种方式就是:
- (void)getRunLoop {
NSRunLoop *runloop = [ NSRunLoop currentRunLoop];
NSRunLoop *manRlp = [NSRunLoop mainRunLoop];
CFRunLoopRef cfRlp = CFRunLoopGetCurrent();
CFRunLoopRef mainCfRlp = CFRunLoopGetMain();
}
看下CFRunLoopGetCurrent
和 CFRunLoopGetMain
的具体实现:
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
发现在过程都调用了_CFRunLoopGet0
这个函数,后面再进行讲解。
CFRunLoopRef源码部分(引入线程相关)
CFRunLoopRef的源码部分
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; //【通过该函数CFRunLoopWakeUp内核向该端口发送消息可以唤醒runloop】
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread; //【RunLoop对应的线程】
uint32_t _winthread;
CFMutableSetRef _commonModes; // 【存储的是字符串,记录所有标记为common的mode】
CFMutableSetRef _commonModeItems;//【存储所有commonMode的item(source、timer、observer)】
CFRunLoopModeRef _currentMode;//【当前运行的mode】
CFMutableSetRef _modes;//【存储的是CFRunLoopModeRef】
struct _block_item *_blocks_head;//【do blocks时用到】
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
对于一些属性之外,重点需要关注三个成员变量
pthread_t _pthread;【RunLoop对应的线程】
CFRunLoopModeRef _currentMode;【当前运行的mode】
CFMutableSetRef _modes;【存储的是CFRunLoopModeRef】
看看RunLoop和线程的关系。
2. RunLoop和线程
先看一下_CFRunLoopGet0
这个函数是怎么实现的,和RunLoop
和线程有什么关系。
//全局的Dictionary,key是pthread_t,value是CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
//访问__CFRunLoops的锁
static CFSpinLock_t loopsLock = CFSpinLockInit;
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
//t==0是始终有效的“主线程”的同义词
//获取pthread对应的RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT