Runloop知识梳理

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)
    Runloop知识梳理

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,一般用不到)
    Runloop知识梳理

  • 实现机制(大致如下)
    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:,这个接口是异步绘制的入口;如若未实现,进行系统绘制流程,绘制结束。
    Runloop知识梳理

  • 系统绘制流程
    创建BackingStore,用于获取图形上下文,然后判断是否有Delegate。有,则调用[layer.delegate drawLayer:inContext:],并返回回调[UIView draw]给我们,让我们在系统绘制的基础上做一些其他事情;没有,则调用[CALayer drawInContext:]。以上两个分支都会将绘制存储到BackingStore中,然后提交到GPU,绘制结束。
    Runloop知识梳理

  • 异步绘制
    在异步绘制入口(上面提到的[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];
}
  • 转载请标明出处
  • 如有错误理解,还请各路大神批评指出
上一篇:精选面试题教你应对高级iOS开发面试官(提供底层进阶规划蓝图)


下一篇:iOS中的系统目录(Documents、tmp、Library)、RunLoop的一些知识点