源码地址: 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如下
基本概念
- RunLoop: 运行循环,内部实现了一个
do while
循环用于处理各种事件timer,source0, source1,block
- Timer: RooLoop中注册的timer事件,RunLoop
-
## CFRunLoopMode
它代表的是RunLoop当前运行的模式,struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; //用于锁定在特定模式下运行
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
..
};
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;
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), - 监听的流程
- 通知观察者运行循环已经进入
__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中找到的命令。
## TODO: Demo