iOS之RunLoop探索

iOS之RunLoop探索

一、RunLoop探索剖析

1.概念

RunLoop 是什么?RunLoop 仍是比较望文生义的一个东西,说白了就是一种循环,只不过它这种循环比较高档。一般的 while 循环会导致 CPU 进入忙等候状况,而 RunLoop 则是一种“闲”等候,这部分能够类比 Linux 下的 epoll。当没有事情时,RunLoop 会进入休眠状况,有事情产生时, RunLoop 会去找对应的 Handler 处理事情。RunLoop 能够让线程在需求做事的时分忙起来,不需求的话就让线程休眠。

附上官方RunLoop与线程的联系如下图:

iOS之RunLoop探索

2.结构体

直接上代码,检查结构:

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* locked for accessing mode list */
    __CFPort _wakeUpPort;   // used for CFRunLoopWakeUp 内核向该端口发送音讯能够唤醒runloop
    Boolean _unused;
    volatile _per_run_data *_perRunData; // reset for runs of the run loop
    pthread_t _pthread;             //RunLoop对应的线程
    uint32_t _winthread;
    CFMutableSetRef _commonModes;    //存储的是字符串,记载一切符号为common的mode
    CFMutableSetRef _commonModeItems;//存储一切commonMode的item(source、timer、observer)
    CFRunLoopModeRef _currentMode;   //当时运转的mode
    CFMutableSetRef _modes;          //存储的是CFRunLoopModeRef
    struct _block_item *_blocks_head;//doblocks的时分用到
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};

可见,一个RunLoop对象,首要包括了一个线程,若干个Mode若干个commonMode,还有一个当时运转的Mode。

RunLoop Mode

一个Mode办理着各种事情,它的结构如下:

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;  //sources0
    CFMutableSetRef _sources1;  //sources1
    CFMutableArrayRef _observers; //告诉
    CFMutableArrayRef _timers;    //定时器
    CFMutableDictionaryRef _portToV1SourceMap; //字典  key是mach_port_t,value是CFRunLoopSourceRef
    __CFPortSet _portSet; //保存一切需求监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中
    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 */
};

source0 是什么?app触发的事情,app办理的事情; UI事情

Runloop Mode 实际上是 Source,Timer 和 Observer 的集合,不同的 Mode 把不同组的 Source,Timer 和 Observer 阻隔开来。Runloop 在某个时刻只能跑在一个 Mode 下,处理这一个 Mode 傍边的 Source,Timer 和 Observer。

苹果文档中说到的 Mode 有五个,分别是:

  • NSDefaultRunLoopMode
  • NSConnectionReplyMode
  • NSModalPanelRunLoopMode
  • NSEventTrackingRunLoopMode
  • NSRunLoopCommonModes

官网地址

iOS之RunLoop探索
翻译解释:
iOS之RunLoop探索

RunLoop Source

Run Loop Source分为Source、Observer、Timer三种,他们统称为ModeItem。

CFRunLoopSource CFRunLoopSource分source0和source1两个版本,它的结构如下:

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits; //用于符号Signaled状况,source0只有在被符号为Signaled状况,才会被处理
    pthread_mutex_t _lock;
    CFIndex _order;         /* immutable */
    CFMutableBagRef _runLoops;
    union {
        CFRunLoopSourceContext version0;     /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;    /* immutable, except invalidation */
    } _context;
};

source0是App内部事情,由App自己办理的UIEvent、CFSocket都是source0。当一个source0事情预备执行的时分,必需求先把它符号为signal状况,以下是source0的结构体:

typedef struct {
    CFIndex version;
    void *  info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
    Boolean (*equal)(const void *info1, const void *info2);
    CFHashCode  (*hash)(const void *info);
    void    (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
    void    (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
    void    (*perform)(void *info);
} CFRunLoopSourceContext;

使用时,你需求先调用 CFRunLoopSourceSignal(source),将这个 Source 符号为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事情。

source1的结构体:

typedef struct {
    CFIndex version;
    void *  info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
    Boolean (*equal)(const void *info1, const void *info2);
    CFHashCode  (*hash)(const void *info);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
    mach_port_t (*getPort)(void *info);
    void *  (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
    void *  (*getPort)(void *info);
    void    (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;

Source1除了包括回调指针外包括一个mach port,Source1能够监听系统端口和通过内核和其他线程通信,接收、分发系统事情,它能够主动唤醒RunLoop(由操作系统内核进行办理,例如CFMessagePort音讯)。官方也指出能够自定义Source,因此关于CFRunLoopSourceRef来说它更像一种协议,框架已经默许定义了两种实现,如果有必要开发人员也能够自定义。

CFRunLoopObserver

CFRunLoopObserver是调查者,能够调查RunLoop的各种状况,并抛出回调。

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 */
};
仿制代码

CFRunLoopObserver能够调查的状况有如下6种:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), //行将进入run loop
    kCFRunLoopBeforeTimers = (1UL << 1), //行将处理timer
    kCFRunLoopBeforeSources = (1UL << 2),//行将处理source
    kCFRunLoopBeforeWaiting = (1UL << 5),//行将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),//被唤醒但是还没开始处理事情
    kCFRunLoopExit = (1UL << 7),//run loop已经退出
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

CFRunLoopTimer

CFRunLoopTimer是定时器,能够在设定的时刻点抛出回调,它的结构如下:

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;  //符号fire状况
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;        //增加该timer的runloop
    CFMutableSetRef _rlModes;     //寄存一切 包括该timer的 mode的 modeName,意味着一个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 */
};

所以CFRunLoopTimer具有以下特性:

  • CFRunLoopTimer 是定时器,能够在设定的时刻点抛出回调
  • CFRunLoopTimer和NSTimer是toll-free bridged的,能够相互转化

3.RunLoop原理

先看RunLoop流程图:

iOS之RunLoop探索
继续看runLoop 的源码:
iOS之RunLoop探索
这个do while 循环是有条件退出或者中止的。所以能够省cpu资源、提供程序的性能:该做事就做事,该歇息就歇息。

继续探索进入CFRunLoopRunSpecific

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {   /* DOES CALLOUT */
  CHECK_FOR_FORK();
  if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
  __CFRunLoopLock(rl);
  /// 首先根据modeName找到对应mode
  CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
  /// 告诉 Observers: RunLoop 行将进入 loop。
  __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
  /// 内部函数,进入loop
  result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
  /// 告诉 Observers: RunLoop 行将退出。
  __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
  return result;
}

在看中心函数__CFRunLoopRun

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
  int32_t retVal = 0;
  do { // itmes do
    /// 告诉 Observers: 行将处理timer事情
    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
    /// 告诉 Observers: 行将处理Source事情
    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
    /// 处理Blocks
    __CFRunLoopDoBlocks(rl, rlm);
    /// 处理sources0
    Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
    /// 处理sources0回来为YES
    if (sourceHandledThisLoop) {
      /// 处理Blocks
      __CFRunLoopDoBlocks(rl, rlm);
    }
    /// 判别有无端口音讯(Source1)
    if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
      /// 处理音讯
      goto handle_msg;
    }
    /// 告诉 Observers: 行将进入休眠
    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
    __CFRunLoopSetSleeping(rl);
    /// 等候被唤醒
    __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
    // user callouts now OK again
    __CFRunLoopUnsetSleeping(rl);
    /// 告诉 Observers: 被唤醒,结束休眠
    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
  handle_msg:
    if (被Timer唤醒) {
      /// 处理Timers
      __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
    } else if (被GCD唤醒) {
      /// 处理gcd
      __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
    } else if (被Source1唤醒) {
      /// 被Source1唤醒,处理Source1
      __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
    }
    /// 处理block
    __CFRunLoopDoBlocks(rl, rlm);
    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)) {
      retVal = kCFRunLoopRunFinished;
    }
  } while (0 == retVal);
  return retVal;
}

小节: runloop依托当时的线程创建(如主线程loop),接收_commonModeItems 使命集合,三种类型的使命(source、timer、observer)根据不同的应用场景 _currentMode状况,切换runloop commonMode 的item 在等候,休眠的使命进入到loop中进行处理。

总结

  1. 结构:一个RunLoop对象,首要包括了一个线程,若干个Mode若干个commonMode,还有一个当时运转的Mode。
    • Runloop Mode 实际上是 Source,Timer 和 Observer 的集合
    • Run Loop Source分为Source、Observer、Timer三种,他们统称为ModeItem。
    • 这里注意Source0是非基于Port,需求手动CFRunLoopWakeUp来唤醒
    • 也就是与Source1的差异直接msg唤醒。
  2. 原理:告诉进入runLoop,然后__CFRunLoopRun起来,判别使命是Source,Timer ,Observer,如果是Source1直接直接唤醒,非Source1的使命就进入休眠,等候msg音讯的唤醒音讯。都执行完结,进行符号回来状况,stop,finish。退出runLoop,do-while 完结。
  3. 应用:检测UI卡顿,应用进程保活。

ps:祝大伙新的一年健健康康,兔飞猛进。

本文部分借鉴了大佬的文章:iOS RunLoop详解