Runloop源码
源码地址: https://opensource.apple.com/tarballs/CF/
官方文档介绍: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW3
RunLoop图解
从下面这张图中可以看到一个RunLoop相关的元素主要有这些,下面的循环对应的就是RunLoop,在应用程序启动之后会首先在主线程开启一个RunLoop循环,不段来处理程序的
timer
事件,来自用户或系统的MatchPort的source
事件,知道它超时或结束.
它的左侧有一个容器形状的线程,很形象生动表达了当前的
RunLoop
被线程所拥有,线程销毁则这个RunLoop也会被销毁runUntilDate:右边的黄色箭头断开后链接说明
RunLoop
超时会退出循环右边代表来自各个线程的
sources
事件
RunLoopMode
- 它主要是为当前的运行循环提供一个特性的执行模式,所有的事件只有在他们所支持的模式下运行,默认情况下
RunLoop
是在kCFRunLoopDefaultMode
下运行,RunLoop在微观上每次只能执行,如果系统有一些优先级较高的事件一直占用某个Mode那么就会阻塞其他Mode上注册的事件,RunLoop
也提供了解决方案,将事件注册到CommonMode
中那么它会在每次RunLoop
循环周期都会去检查是否有未处理的事件需要执行,当某个事件需要在多个Mode下执行的时候可以考虑将其加入到CommonModes中。C struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; //Mode名称
Boolean _stopped; //Mode是否已经停止工作
char _padding[3];
CFMutableSetRef _sources0; //Source0事件集合,用户相关的事件
CFMutableSetRef _sources1; //Source1事件集合,来自MatchPort内核相关的事件,如IOKit的事件分发
CFMutableArrayRef _observers; //观察者
CFMutableArrayRef _timers; //timer事件
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet; //用于接收来自MatchPort的事件
CFIndex _observerMask;
mach_port_t _timerPort; //用于接收来自timerPort的事件
Boolean _mkTimerArmed;
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
}; - 系统常用的Mode如下
InputSource
Input Source
异步的发送事件到当前线程,输入源的事件由它的类型决定,通常情况下由两种类型。- 一种是监听应用程序Match端口的输入,另外一种则是监听用户自定义事件.
- 对于运行循环而言,输入源是基于端口或者是自定义并不重,操作系统会实现两种输入源类型,取决于他们如何被触发,基于端口的source1是由kernel自动触发的
- 自定义的事件source0只能由其他线程手动去触发
- Port-Based Sources: Cocoa和CoreFunction框架提供了一些类支持我们创建基于端口的事件源,如
NSPort
,(CFMachPortRef, CFMessagePortRef, or CFSocketRef)
- Custom Input Sources: CoreFunction提供了
CFRunLoopSourceRef
,可以通过它来创建自定义的输入源,需要设置如何处理将要执行的事件,怎样关闭source当它从RunLoop中移除时,同时也需要对它的事件机制进行定义 Cocoa Perform Selector Sources: Cocoa框架实现了一一个自定义的输入源允许直接在某个线程上执行方法,利用多线程协同处理的方式很大的程度上减少了主线程的阻塞
- 需要主意的是,当在某个线程执行方法时,这个线程需要有自己的
RunLoop
,如果是子线程需要自己手动的开启RunLoop. - 可以在应用程序启动之后就开启这个线程的
RunLoop
,这样就能够处理队列上的所有方法 - 常用的方法如下
```Objective-C
performSelectorOnMainThread:withObject:waitUntilDone
performSelectorOnMainThread:withObject:waitUntilDone:modes:
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:- __CFRunLoopSource ```Objective-C
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};TimerSources
- 事件源会在指定的时间同步的发送一个事件到当前线程,它是线程自我通知的一种实现方式,通常可以用来做连续事件的过滤处理,比如频繁的点击,频繁的文本搜索输入,通过设定一定的时间buff,可以减少频繁冗余的方法调用
- 尽管它实现了一个基于时间的通知,但它并不是一个真正的timer运行机制,和大都数的input source一样,Timer它也有自己运行的Mode和RunLoop,如果Timer运行的Mode不在当前的RunLoop的观察下,它将不会执行,同样的当一个计时器触发时,如果运行循环正在处理程序例程,timer将不会立即执行,直到下一个RunLoop再去回调它,如果这个RunLoop被销毁它将不会执行。
- CFRunLoopTimer
Objective-C struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFMutableSetRef _rlModes; //Timer可以支持在多个mode下运行
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; /* immutable */
CFTimeInterval _tolerance; /* mutable */
uint64_t _fireTSR; /* TSR units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; /* immutable */
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
- 需要主意的是,当在某个线程执行方法时,这个线程需要有自己的
Run Loop Observers
- 监听
RunLoop
的生命周期,Objective-C struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; /* immutable */
CFRunLoopObserverContext _context; /* immutable, except invalidation */
}; - 对应6种状态的监听
Objective-C kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7), - 监听的流程也是RunLopp的运行流程
- 通知观察者运行循环已经进入
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
- 通知观察者事件触发器已经就绪
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
- 通知观观察者非端口事件已经就绪
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
- 执行非端口且准备完成的事件源,(block事件)
__CFRunLoopDoBlocks(rl, rlm);
- 如果一个基于口的输入源以及准备完毕并且等待执行,跳转到第9步
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL))
- 通知观察者
RunLoop
将要开始休眠__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting)
,__CFRunLoopSetSleeping(rl)
- 保持线程一直处于睡眠状态,知道接收到如下事件
- 一个机基于端口的事件
__CFRunLoopServiceMachPort
- timer事件触发
if (livePort == rl->_wakeUpPort)
- 为运行循环设置的超时值过期
- RunLoop显示的被唤醒
- 一个机基于端口的事件
- 通知观察者线程被唤醒
- 处理pending的事件
- 处理timer事件
- 处理input source事件
- 通知观察者RunLoop已经退出运行循环
- 通知观察者运行循环已经进入
RunLoop定义
struct __CFRunLoop {
CFRuntimeBase _base; //不负责自动引用计数
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; //RunLoop被唤醒时的port // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; //重制当前RunLoop的状态
pthread_t _pthread; //当前RunLoop所运行的线程
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
- RunLoop运行的状态
_perRunData->stopped = 0x53544F50; // 'STOP'
_perRunData->ignoreWakeUps = 0x57414B45; // 'WAKE'
__CFRunLoopSetSleeping
__CFRunLoopSetDeallocating - RunLoop自带一个互斥锁,用于锁定当前线程资源,避免多线程冲突
CF_INLINE void __CFRunLoopLockInit(pthread_mutex_t *lock) {
pthread_mutexattr_t mattr;
pthread_mutexattr_init(&mattr);
pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
int32_t mret = pthread_mutex_init(lock, &mattr);
pthread_mutexattr_destroy(&mattr);
if (0 != mret) {
}
}
创建RunLoop
- (void)mainThread {
// The application uses garbage collection, so no autorelease pool is needed.
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create a run loop observer and attach it to the run loop.
CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
if (observer)
{
CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];
CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
}
// Create and schedule the timer.
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
NSInteger loopCount = 10;
do
{
// Run the run loop 10 times to let the timer fire.
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
loopCount--;
}
while (loopCount);
}
Configuring Run Loop Sources
创建自定于源需要具备以下信息
- 输入源的处理信息,CFRunLoopSource的事件
- 调度函数,让客户端知道什么时候需要处理事件
- 一个callback回调函数
- 输入源销毁的函数(cancellation)
下图描述了2个线程之间的RunLoop的通信过程,应用程序
Main Thread
维护了对Input source
的引用,Main Thread的任务通过Command Buffer
将事件传递给WorkerThread
,(因为Worker Thread的Input Source和Main Thread都可以访问Command Buffer,所以必须同步访问。)一旦发出命令,Main Thread就向Input Source发出信号,并唤醒Worker Thread的Runloop。在接收到wake up命令后,Runloop调用Input Source的处理程序,该处理程序处理在Command Buffer中找到的命令。
RunLoop与GCD的关系
- GCD是Grand Center Dispatch,由系统底层API调用,精度更高,在RunLoop的实现中,才用了GCD来监听RunLoop是否超时
- 执行GCD派发的block任务时,如
dispatch_async(dispatch_get_main_queue(), block)
会向主线成的RunLoop
发送信息,并在CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
执行具体的block事件 - 其他线程如果有设置
RunLoop
也是如此.主要是看GCD的block派发到哪个线程,子线程默认不会开启RunLoop
RunLoop与内存关系
- 通过在RunLoop开启和结束注册2个不同Observers来实现的RunLoop创建和结束监听
- 监听到开始事件时通过
_objc_autoreleasePoolPush
创建自动释放池, - 监听到结束事件时通过
_objc_autoreleasePoolPop
销毁释放池
RunLoop与事件响应
- 系统注册了一个Source1的输入源,用来接收系统事件,其回调函数是
__IOHIDEventSystemClientQueueCallback
,当接收到来自硬件的事件时,首先由IOKit.framework
生成一个IOHIDEvent
事件,并交由SpringBoard
接收, - SpringBoard接收按键(锁屏/静音等),触摸,加速,接近传感器等事件,通过mach port 转发给需要的App进程。
- 进而触发
Source1
事件的回调,通过_UIApplicationHandleEventQueue
将事件派发到应用程序的事件对类上,UIApplication接收到事件后再往下传递 _UIApplicationHandleEventQueue
会将IOHIDEvent
包装成UIEvent
进行分发。
利用RunLoop保持线程常驻
- 例如在AFNetworking中开启了一个常驻线程用来处理网络请求事件,避免了线程的频繁创建销毁所带来的开销
- 在对应的线程开启一个RunLoop,设置MatchPort作为它的输入源,然后开启一个运行循环
addPort:forMode: 在RunLoop指定模式下添加一个输入源
+ (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
//通常情况其它线程访问这个pot就能给这个线程的RunLoop发送消息,此处只是为了让那个RunLoop有事件源被监听,避免退出
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}通过dispatch once token,原子执行,安全的生成一个线程,下面的例子开启了一个信息的线程
AFNetworking
(在这个线程内部的bloc块中已经设置了它的名字)/// target /// - 只接收消息的对象
/// selector
/// - 用于发送消息给对象(target),此方法只能有一个参数并且不能有返回值
/// argument
/// - 发送给target的参数
/// - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
//创建一个新的线程
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
//异步开启一个新的线程,并在此线程上调用它的入口方法
[_networkRequestThread start];
});
return _networkRequestThread;
}