Runloop知识梳理
- 源码可在这里查看:https://opensource.apple.com/tarballs/CF/
1、NSTimer
- NSTimer解析:
NSTimer其实是CFRunloopTimerRef,他们之间是对象桥接(toll-free bridged)的关系。一个timer添加至runloop中,runloop会为其注册好重复时间点的事件,比如12:00,12:10,12:20等。但是,runloop为了节省资源,并不会在准确的时间点回调timer事件,tolerance属性就是设置最大允许的时间容差。
NSTimer *timer = [[NSTimer alloc] init];
timer.tolerance = 1.0;
-
NSTimer有时不好使的原因:
NSTimer默认创建是添加至runloop的defaultMode模式下,当runloop的mode发生变化时,当前的NSTimer并不会执行 -
补充
CADisplayLink是与屏幕刷新率一致的定时器。但是,当帧与帧之间执行一个比较复杂的任务时,会出现掉帧的情况,导致界面卡顿
2、autoreleasePool
- 原理:autoreleasePoolPage双向连接而成(双向链表,autoreleasePoolPage相当于一个node)
- 释放时机:App启动后,苹果会注册两个observer,它们的回调都是_wrapRunloopWithAutoreleasePoolHandler()。
**a、**第一个observer监听的是entry事件,其回调内会调用_objc_autoreleasePoolPush()创建释放池,它的优先级最高,保证它创建在最前面。**b、**第二observer监听两个事件:
1)beforeWaiting事件:beforeWaiting事件回调中,会调用_objc_autoreleasePoolPop()和_objc_autoreleasePoolPush()释放旧的释放池和创建新的释放池;2)exit事件:
在exit事件回调中,调用_objc_autoreleasePoolPop()释放新的释放池,其优先级最低,保证它在最后执行 - autoReleasePoolPage大致的基本结构如下图所示(大小:4096bytes=4k)
3、Runloop
- 数据结构:NSRunloop是对CFRunLoop的封装,提供面向对象的API,大致可分成5个大类:__CFRunloop、__CFRunloopMode、__CFRunloopSource、__CFRunloopTimer和__CFRunloopObserver。
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopObserver * CFRunLoopObserverRef;
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
__CFRunloopMode并未对外暴漏
a、***__CFRunloop***:由pthread线程对象、modes(多个运行模式)、currentMode(当前模式类型)、commonModes(多个运行模式的字符串名称)和commonModeItems(source、timer、observer组成)等组合而成。
/**
* 源码结构如下
**/
struct __CFRunLoop {
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 runloop
pthread_t _pthread;//线程对象,与runloop一一对应
uint32_t _winthread;
CFMutableSetRef _commonModes;//运行模式的字符串名称集合
CFMutableSetRef _commonModeItems;//source、timer、obverser
CFRunLoopModeRef _currentMode;//当前runloop运行模式
CFMutableSetRef _modes;//多个运行模式集合
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
b、***__CFRunloopMode***:由source0、source1、timers、observers等组成
//源码结构
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
c、***__CFRunloopSource***:分source0和source1两种;source0非基于port的,即用户触发的事件,需要手动唤醒线程,将当前线程由内核态切换到用户态;source1基于port的,监听系统端口与通过内核和其他线程发送的消息,能主动唤醒runloop,接收分发系统事件。
//源码结构如下
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;
};
d、***__CFRunloopTimer***:基于时间的触发器,基本说的是NSTimer。在预设的时间点唤醒runloop执行回调(因为它是基于runloop,因此它不是实时的,因为runloop只负责分发源消息,如果当前线程在执行复杂的任务,就会延迟执行timer回调,甚至会跳过当前时间点的回调事件)
//源码结构如下
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 */
};
e、***__CFRunloopObserver***:监听如下时间点,kRunloopActivity、kRunloopEntry(runloop准备启动)、kRunloopBeforeTimers(runloop即将要处理一些timer相关事件)、kRunloopBeforeSources(runloop即将要处理一些source相关事件)、kRunloopBeforeWaiting(runloop即将进入休眠状态,由用户态切换成内核态)、kRunloopAfterWaiting(runloop被唤醒,由内核态切换成用户态)、kRunloopExit(runloop退出)和kRunloopAllActivities(监听所有状态)
//源码结构如下
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 */
};
-
Mode:关于Mode首先要知道,runloop可能包含多个mode,且每次调用runloop主函数只能指定一个mode(currentMode)。切换mode,只能重新指定一个mode。这样的目的是分开不同的source、timer、observer,使它们互不影响。当runloop运行在mode1上时,是无法接收mode2、mode3的source、timer、observer的。
总共有5种mode:kRunloopDefaultMode(默认模式,主线程在这个模式下执行)、kRunloopCommonModes(伪模式,是同步source/timer/observer到多个mode的一种方案)、kUITrackingRunloopMode(跟踪用户交互,滚动触摸等)、kInitializationRunloopMode(app刚启动进入的第一个mode,启动完就不用)、GSEventReceiveRunloopMode(接收系统内部事件mode,一般用不到) -
实现机制(大致如下)
1、通知观察者runloop即将启动
2、通知观察者runnloop即将处理timer事件
3、通知观察者runloop即将处理sourece0事件
4、处理source0事件
5、如果有基于端口的source1事件并处于等待状态,进入步骤9
6、通知观察者线程即将进入休眠状态
7、将线程设置成休眠状态,下面的任一事件都会唤醒线程:(1)基于port的source1事件(2)timer事件(3)runloop自身时间到
8、通知观察者线程即将被唤醒
9、处理唤醒时收到的事件:(1)用户定义的定时器启动,处理事件并重启runloop,进入步骤2(2)输入源启动,传递相应信息(3)runloop被显式唤醒但runloop自身时间未到,重启runloop,进入步骤2
10、通知观察者runloop结束 -
常驻线程
1、为当前线程开启一个runloop
2、向runloop中添加port/source维持runloop循环
3、开启runloop -
作用
runloop的主要作用是保证无消息时线程处于休眠状态,有消息时唤醒线程,节省资源和提高程序性能。
4、事件
-
事件响应过程
当一个硬件事件(触摸、锁屏、旋转等)发生后,由IOKit.framework生成一个IOHIDEvent事件;事件由SpringBoard接收,通过mach_port分发至App进程,随后通过注册的source1回调将IOHIDEvent包装成UIEvent事件进行处理和分发。 -
手势识别过程
在source1回调中识别到一个手势后,首先调用cancel打断touchBegin/Move/End系列事件,然后将识别到的手势标志为待处理。苹果注册了observer监听beforeWaiting事件,在这个回调中会拿取待处理的手势事件并进行相应的处理
5、渲染
-
runloop渲染过程
当调用[UIView setNeedsDisplay]时,回调用UIView layer的setNeedsDisplay方法,相当于给layer打一个标志。这时并未直接进行绘制工作,而是到当前runloop的beforeWaiting才会进行绘制工作。调用[CALayer display]进行绘制工作。首先判断是否实现layer.delegate的方法displayer:,这个接口是异步绘制的入口;如若未实现,进行系统绘制流程,绘制结束。 -
系统绘制流程
创建BackingStore,用于获取图形上下文,然后判断是否有Delegate。有,则调用[layer.delegate drawLayer:inContext:],并返回回调[UIView draw]给我们,让我们在系统绘制的基础上做一些其他事情;没有,则调用[CALayer drawInContext:]。以上两个分支都会将绘制存储到BackingStore中,然后提交到GPU,绘制结束。 -
异步绘制
在异步绘制入口(上面提到的[layer.delegate displayer:])中使用子线程将所需要的内容绘制好,通过bitmap为layer.contents属性赋值
6、补充
- Runloop在AFNetworking中的运用
AFNetworking在runloop启动前添加了一个NSMachPort,目的是为了runloop不退出。代码实现如下
- (NSThread *)networkRequestThread{
static NSThread *_networkRequestThread = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
- (void)networkRequestThreadEntryPoint:(id)__unused object{
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runloop run];
}
- 转载请标明出处
- 如有错误理解,还请各路大神批评指出