- 所有的RunLoop保存在一个全局Dictionary里,线程作为key,RunLoop作为value。
- RunLoop都是通过懒加载的方式创建。
- 子线程RunLoop需要手动创建,主线程RunLoop在UIApplicationMain中创建。
以下是RunLoop,RunLoopMode和Source具体实现代码:
struct __CFRunLoop {
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
Boolean _stopped;
char _padding[3];
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
mach_port_t _timerPort;
Boolean _mkTimerArmed;
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order; /* 优先级,越小,优先级越高*/
CFMutableBagRef _runLoops;
union { // 联合,用于保存source的信息,同时可以区分source是0还是1类型
CFRunLoopSourceContext version0;
CFRunLoopSourceContext1 version1;
} _context;
};
每个RunLoop包含多种模式(_modes),但同时只能处于一种模式(_currentMode)。
每种模式下又包含_sources0,_sources1,_observers,_timers四种事件,它们都是集合类型。
RunLoop在切换模式时,会先退出当前loop,再重新进入一个新的loop。
不同Mode下的Source0/Source1/Timer/Observer被分隔开来,互不影响。一个Source0/Source1/Timer/Observer,可以被加入到多个Mode下。
如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出。
mode类型(_name):
- kCFRunLoopDefaultMode:
App的默认Mode - UITrackingRunLoopMode:
界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响 - UIInitializationRunLoopMode:
在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode - GSEventReceiveRunLoopMode:
接受系统事件的内部 Mode,通常用不到 - kCFRunLoopCommonModes:
这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
处理事件:
- source0(偏向应用层)
- 触摸事件的处理
-
performSelector:onThread:
- source1(由RunLoop和内核管理,Mach Port驱动)
- 线程间通信
- 系统事件的捕捉(比如触摸屏幕由source1捕捉,再交给source0处理)
- 网络请求接收CFSocket 的通知,并交给Source0处理。
- Timer
- NSTimer
- performSelect:withObject:afterDelay:
- Observers
- 监听Runloop状态
主线程默认监听BeforeWaiting执行界面更新
主线程默认监听Entry执行_objc_autoreleasePoolPush()
主线程默认监听BeforeWaiting执行_objc_autoreleasePoolPop() + _objc_autoreleasePoolPush()
主线程默认监听Exit执行_objc_autoreleasePoolPop()
主线程默认监听BeforeWaiting执行Core Animation
Runloop状态
/* Run Loop Observer Activities */
public struct CFRunLoopActivity : OptionSet {
public init(rawValue: CFOptionFlags)
public static var entry: CFRunLoopActivity { get }
public static var beforeTimers: CFRunLoopActivity { get }
public static var beforeSources: CFRunLoopActivity { get }
public static var beforeWaiting: CFRunLoopActivity { get } // 即将进入休眠
public static var afterWaiting: CFRunLoopActivity { get } // 刚从休眠唤醒
public static var exit: CFRunLoopActivity { get }
public static var allActivities: CFRunLoopActivity { get }
}
实现以下代码,监听主线程default模式下Runloop状态的切换。
// viewDidLoad
let observer = CFRunLoopObserverCreate(kCFAllocatorDefault, CFRunLoopActivity.allActivities.rawValue, true, 0, observeRunLoopActivities(), nil)
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, .defaultMode)
func observeRunLoopActivities() -> CFRunLoopObserverCallBack {
return {(observer, activity, context) -> Void in
switch(activity) {
case CFRunLoopActivity.entry:
print("Run Loop已经进入Loop")
case CFRunLoopActivity.beforeTimers:
print("Run Loop分配定时任务前")
case CFRunLoopActivity.beforeSources:
print("Run Loop分配输入事件源前")
case CFRunLoopActivity.beforeWaiting:
print("Run Loop休眠前")
case CFRunLoopActivity.afterWaiting:
print("Run Loop被唤醒")
case CFRunLoopActivity.exit:
print("Run Loop已经退出Loop")
default:
break
}
}
}
运行程序,发现log中并没有Run Loop已经进入Loop
,这是因为开启监听的时候,Runloop早已经启动了。
当app处于“静止”时,滑动scrollView,会显示Run Loop被唤醒
,然后执行一系列任务后,log显示Run Loop已经退出Loop
,这印证了前面所说的当Runloop切换mode时,Runloop会先退出当前loop。
此时滑动scrollView,不会有任何log,因为default模式下的observer监听不到tracking模式的状态变化。
当停止滑动时,Runloop又切换到default模式,log显示Run Loop已经进入Loop
。
如果将代码改成如下,将observer加入到commonModes:
let observer = CFRunLoopObserverCreate(kCFAllocatorDefault, CFRunLoopActivity.allActivities.rawValue, true, 0, observeRunLoopActivities(), nil)
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, .commonModes)
func observeRunLoopActivities() -> CFRunLoopObserverCallBack {
return {(observer, activity, context) -> Void in
switch(activity) {
case CFRunLoopActivity.entry:
print("Run Loop已经进入Loop: ", CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()) ?? "")
case CFRunLoopActivity.exit:
print("Run Loop已经退出Loop: ", CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()) ?? "")
default:
break
}
}
}
当滑动scrollView时,输出:
Run Loop已经退出模式: CFRunLoopMode(rawValue: kCFRunLoopDefaultMode)
Run Loop已经进入模式: CFRunLoopMode(rawValue: UITrackingRunLoopMode)
当停止滑动时,输出:
Run Loop已经退出模式: CFRunLoopMode(rawValue: UITrackingRunLoopMode)
Run Loop已经进入模式: CFRunLoopMode(rawValue: kCFRunLoopDefaultMode)
RunLoop的运行逻辑如下图:
RunLoop被唤醒的事件包括:
- 一个基于mach port 的Source 的事件(source1)
- 一个 Timer 到时间了
- RunLoop 自身的超时时间到了
- 被其他调用者手动唤醒
source0无法主动唤醒RunLoop。
也可通过如下函数将指定block交给RunLoop执行。
CFRunLoopPerformBlock(CFRunLoopGetCurrent(), CFRunLoopMode.commonModes.rawValue) {
// do something
}
RunLoop的入口函数是CFRunLoopRun()
。当切换模式时,会调用CFRunLoopRunInMode()
,并传入新的mode。
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
CFRunLoopRunSpecific实现如下:
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
// ...
// 通知Observers:进入Loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 进入RunLoop循环
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知Observers:退出Loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
// ...
return result;
}
在源码中可以看到大量USE_DISPATCH_SOURCE_FOR_TIMERS
和USE_MK_TIMER_TOO
的定义,前者代表gcd定时器,后者代表普通定时器。在 macOS 下则同时支持使用 dispatch_source 和 MK_TIMER 来构建RunLoop定时器,其他平台则只支持 MK_TIMER。
#if DEPLOYMENT_TARGET_MACOSX
#define USE_DISPATCH_SOURCE_FOR_TIMERS 1
#define USE_MK_TIMER_TOO 1
#else
#define USE_DISPATCH_SOURCE_FOR_TIMERS 0
#define USE_MK_TIMER_TOO 1
#endif
__CFRunLoopRun简化后的实现代码:
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
// ...
int32_t retVal = 0;
do {
// 通知Observers:即将处理Timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// 通知Observers:即将处理Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 处理自定义RunLoop blocks
__CFRunLoopDoBlocks(rl, rlm);
// 处理source0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
// 处理自定义RunLoop blocks
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
// 判断是否有source1
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
}
didDispatchPortLastTime = false;
// 通知Observers:即将休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
// 设置状态为休眠
__CFRunLoopSetSleeping(rl);
// 进入休眠
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// 设置状态为结束休眠
__CFRunLoopUnsetSleeping(rl);
// 通知Observers:已经唤醒
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
// 唤醒方式
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
}
// 定时器唤醒
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time()))
}
// gcd唤醒
else if (livePort == dispatchPort) {
// 处理gcd
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else {
// source1唤醒
// 处理source1
CFRUNLOOP_WAKEUP_FOR_SOURCE();
}
// 处理自定义RunLoop blocks
__CFRunLoopDoBlocks(rl, rlm);
// 确定下一步是否需要退出RunLoop
if (sourceHandledThisLoop && stopAfterHandle) {
// 设置处理完事件就返回
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
// 超时
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
// 被外部调用者强制停止了
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
// 被外部调用者强制停止了
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
return retVal;
}
CFRunLoop的操作最终都会调用一系列全大写命名的函数,在CFRunLoop.c文件中有具体定义。
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__();
// ...
gcd一般不依赖于RunLoop,但是gcd切换回主线程执行的任务将会交给RunLoop执行。
DispatchQueue.main.async {
print("123")
}
可以看到主队列任务由RunLoop执行。
__CFRunLoopServiceMachPort
内部会调用内核函数mach_msg()
使得线程进入休眠状态。
用户点击手机屏幕的处理过程
事件传递链和响应链深度分析:https://www.jianshu.com/p/f9967f1a0ac1
App外:用户点击->硬件响应->参数量化->数据转发->App接收
当一个硬件事件(触摸/锁屏/摇晃等)发生之后,屏幕硬件会接受用户的操作,并采集关键的参数传递给IOKit.framework,而IOKit将这些数据打包成IOHIDEvent,并传给SpringBoard.app,继而通过mach port转发给前台App。
App内:子线程接收事件(source1)->主线程封装事件(source0)->UIWindow启动hitTest确定目标视图(事件传递链)->UIApplication开始发送事件(事件响应链)->touch事件开始回调
子线程接收事件:App启动时便会启动一个com.apple.uikit.eventfetch-thread子线程,负责接收SpringBoard.app转发过来的数据。
eventfetch-thread在RunLoop注册了一个source1,回调方法是__IOHIDEventSystemClientQueueCallback。
SpringBoard会利用mach port,产生source1,来唤醒目标APP的com.apple.uikit.eventfetch-thread的RunLoop,并触发__IOHIDEventSystemClientQueueCallback回调。
__IOHIDEventSystemClientQueueCallback内部会将main RunLoop 中__handleEventQueue所对应的source0设置为signalled == YES状态,同时唤醒main RunLoop。mainRunLoop则调用__handleEventQueue进行事件队列处理。
主线程封装事件:
主线程同样在启动时监听source0,回调方法是__handleEventQueue。
__handleEventQueue会调用 _UIApplicationHandleEventQueue() ,_UIApplicationHandleEventQueue()接收eventfetch-thread线程发送的IOHIDEvent数据,再封装成UIEvent,根据UIEvent的类型判断是否需要启动hitTest。motion事件不需要hitTest,touch事件也有部分不需要hitTest,比如说touch结束触发的事件。
接下来是事件传递链的过程,寻找响应事件的目标视图。
确定目标视图之后,就会创建UITouch,UIApplication将UITouch和UIEvent发送给目标视图,触发其touches系列的方法。
手势识别
当时间传递链找到目标视图后,会开始手势识别。当手势识别成功之后,默认会cancel后续touch操作,从目标视图开始的响应链都会收到touchesCancelled方法,而不是正常的touchesEnded方法。
也可以通过设置下面的cancelsTouchesInView=NO来避免触发touchesCancelled方法。
随后系统将对应的 UIGestureRecognizer 标记为待处理。苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并向APP的event queue中投递一个gesture event。RunLoop处理source0时,最终会回调到我们自己所写的gesture回调中。
当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。
UIButton
UIButton是UIControl的子类,通过追踪touch事件的变化得到一些UIControl定义的事件(UIControlEvents);UIButton的点击操作是通过UIControlEvents的事件变化回调来触发,本质依赖的是响应链回调过程中的touches系列方法。
界面更新
当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并通过CATransaction提交到一个全局的容器去。
苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并提交到 GPU 去显示;如果此处有动画,Core Animation 会通过 DisplayLink 等机制多次触发相关流程。
定时器
NSTimer 其实就是 CFRunLoopTimerRef,他们之间是 toll-free bridged 的。一个 NSTimer 注册到 RunLoop 后,RunLoop 会为其重复的时间点注册好事件。例如 10:00, 10:10, 10:20 这几个时间点。RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer。Timer 有个属性叫做 Tolerance (宽容度),标示了当时间点到后,容许有多少最大误差。
如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。就比如等公交,如果 10:10 时我忙着玩手机错过了那个点的公交,那我只能等 10:20 这一趟了。
CADisplayLink 是一个和屏幕刷新率一致的定时器(但实际实现原理更复杂,和 NSTimer 并不一样,其内部实际是操作了一个 Source)。如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去(和 NSTimer 相似),造成界面卡顿的感觉。在快速滑动TableView时,即使一帧的卡顿也会让用户有所察觉。Facebook 开源的 AsyncDisplayLink 就是为了解决界面卡顿的问题,其内部也用到了 RunLoop。
线程保活
比如子线程内请求一个网络数据,但是等待完成需要等一会,这就会导致得到数据的时候子线程就被回收掉了。要想子线程一直存在,我们可以利用RunLoop,在子线程写下如下代码就可以使得线程保活。
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
添加一个mach port防止RunLoop主动退出。
AFNetworking早期版本使用NSURLConnection发送网络请求,NSURLConnection的特点是每个请求都需要重新创建。AFNetworking维护了一个常驻线程来负责请求的发起与接收,避免频发开辟线程。
AFN参考了MVCNetworking : https://developer.apple.com/library/archive/samplecode/MVCNetworking/Introduction/Intro.html
[self performSelector:@selector(operationDidStart)
onThread:[[self class] networkRequestThread]
withObject:nil
waitUntilDone:NO
modes:[self.runLoopModes allObjects]]
卡顿监控
卡顿监控的本质是通过监测主线程Runloop执行loop(未进入休眠)的时长判断是否存在卡顿。当执行loop的时间过长时,将会导致主线程卡住,无法及时响应触摸等事件。
常见的实现方式是在子线程通过 while循环+信号量 来保证子线程的存活,
并通过信号量的超时机制,连续一定次数超时则认为存在卡顿。
// 信号
semaphore = dispatch_semaphore_create(0);
// 注册RunLoop状态观察
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES,
0,
&runLoopObserverCallBack,
&context);
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
PerformanceMonitor *moniotr = (__bridge PerformanceMonitor*)info;
moniotr->activity = activity;
dispatch_semaphore_t semaphore = moniotr->semaphore;
// RunLoop状态变化释放信号量
dispatch_semaphore_signal(semaphore);
}
// 在子线程监控时长
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (YES)
{
// RunLoop状态变化或超时获取信号量
long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));
// 超时
if (st != 0)
{
// 当RunLoop状态在kCFRunLoopBeforeSources或kCFRunLoopAfterWaiting停留了超过250ms认为存在卡顿(未进入休眠kCFRunLoopBeforeWaiting)
if (activity==kCFRunLoopBeforeSources || activity==kCFRunLoopAfterWaiting)
{
if (++timeoutCount < 5)
continue;
NSLog(@"卡顿");
}
}
timeoutCount = 0;
}
});
性能优化
案例:tableView的Cell中有多个ImageView,同时加载大图,导致UI卡顿。
解决思路:使用Runloop每次循环址添加一张图片。
1、把加载图片等代码保存起来,先不执行 (保存一段代码,block)
2、监听Runloop循环(CFRunloopObserver)
3、每次都从任务数组中取出一个加载图片等代码执行(执行block代码)
通过以下代码,监听即将进入休眠kCFRunLoopBeforeWaiting。
defaultModeObserver = CFRunLoopObserverCreate(NULL,
kCFRunLoopBeforeWaiting, YES,
NSIntegerMax - 999,
&Callback,
&context);
//添加当前runloop的观察着
CFRunLoopAddObserver(current, defaultModeObserver, kCFRunLoopDefaultMode);
static void Callback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
//如果没有任务,就直接返回
if (tasks.count == 0) {
return;
}
BOOL result = NO;
while (result == NO && tasks.count) {
//取出任务
RunloopBlock unit = tasks.firstObject;
//执行任务
result = unit();
//删除任务
[tasks removeObjectAtIndex:0];
}
}
通过定时器唤醒RunLoop,避免RunLoop一直处于休眠。
[NSTimer scheduledTimerWithTimeInterval:0.01 repeats:YES block:^(NSTimer * _Nonnull timer) {
if (tasks.count == 0) {
[timer invalidate];
}
}];
Texture(AsyncDisplayKit)
UI 线程中一旦出现繁重的任务就会导致界面卡顿,这类任务通常分为3类:排版,绘制,UI对象操作。
排版通常包括计算视图大小、计算文本高度、重新计算子式图的排版等操作。
绘制一般有文本绘制 (例如 CoreText)、图片绘制 (例如预先解压)、元素绘制 (Quartz)等操作。
UI对象操作通常包括 UIView/CALayer 等 UI 对象的创建、设置属性和销毁。
其中前两类操作可以通过各种方法扔到后台线程执行,而最后一类操作只能在主线程完成。Texture(AsyncDisplayKit)所做的,就是尽量将能放入后台的任务放入后台,不能的则尽量推迟 (例如视图的创建、属性的调整)。
Texture模拟了RunLoop的CA界面更新机制:所有针对ASNode的修改和提交,总有些任务是必须放入主线程执行的。当出现这种任务时,ASNode会把任务用ASAsyncTransaction(Group)封装并提交到一个全局的容器去。Texture也在Runloop中注册了一个Observer,监视的事件和CA一样,但优先级比CA要低。当Runloop进入休眠前、CA处理完事件后,Texture就会执行该loop内提交的所有任务。通过这种机制,Texture可以在合适的机会把异步、并发的操作同步到主线程去,并且能获得不错的性能。
Texture监听RunLoop的kCFRunLoopBeforeWaiting状态实现代码:https://github.com/TextureGroup/Texture/blob/b7cd0b16567a9eb10e58f4cc0886a145dc5273b8/Source/Details/Transactions/_ASAsyncTransactionGroup.m