RunLoop

RunLoop简介

RunLoop,就是一个运行循环,通过一个内部的运行循环(Event Loop)对事件或者消息管理的一个对象
他是通过一个 do while循环来保持一致运行的(main函数不会退出的原因),然后通过sleep机制来降低CPU的占用问题
特点:

  • 有消息要处理时,唤醒RunLoop来处理事件
  • 没有消息时,进行休眠,避免占用太多资源
  • 和线程是一一对应的
void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

RunLoop 和线程的关系

苹果不允许我们直接创建RunLoop,提供了两个获取的函数:CFRunLoopGetMainCFRunLoopGetCurrent,
RunLoop和线程一一对应
只能操作当前线程的RunLoop,不能操作其他线程
RunLoop在第一次获取时创建([NSRunLoop current]),在线程结束时销毁
源码解析

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}
//__CFRunLoops 是一个字典
static CFMutableDictionaryRef __CFRunLoops = NULL;

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
 CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        
    // 进行绑定 dict[@"pthread_main_thread_np"] = mainLoop
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
//将dict保存在__CFRunLoops中,释放dict
       if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
//获取loop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
//如果没有获取到创建新的
   if (!loop) {
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
//获取loop
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
//如果没有获取到创建新的
    if (!loop) {
//进行绑定
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
   __CFUnlock(&loopsLock);
    CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

  • 如果不存在__CFRunLoops,创建要一个CFMutableDictionaryRef
  • 根据当前线程创建一个CFRunLoopRef
  • 通过kvc将线程做为key,loop作为value存入字典,简历对应关系

RunLoop结构,如何创建

刚才研究RunLoop和线程的关系,可以看出RunLoop是通过__CFRunLoopCreate ()方法创建的,来看下他的实现

static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
    CFRunLoopRef loop = NULL;
    CFRunLoopModeRef rlm;
    uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
    loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
    .....
  return loop;
}

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 run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};
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
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};
  • RunLoop是一个CFRunLoopRef对象.有_pthread,_commonModes,_commonModeItems,等属性
  • __CFRunLoopMode,有_sources0, _sources1, _observers,_timers等属性
  • RunLoop跟线程是一对一绑定关系
  • RunLoop 有多个CFRunLoopMode
  • 一个CFRunLoopMode有多个CFRunLoopSource CFRunLoopTimer CFRunLoopObserver
    RunLoop结构

Timer,Source,Block等是如何加入到RunLoop中并且执行的

我们在使用Timer时往往会遇到一些问题,比如timer运行时我们滑动界面,这个时候timer就会停止运行,因为timer的运行是依赖于RunLoop的,创建出来默认是在defaultModel中的,当我们滑动界面时Model就会从defaultModel变为UITrackingRunLoopMode,Model只能存在一个所以这个时候 Timer暂停运行,这个时候我们往往会这样写

NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"fire in home -- %@",[[NSRunLoop currentRunLoop] currentMode]);
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode: UITrackingRunLoopMode];

那么[[NSRunLoop currentRunLoop] addTimer:timer forMode: UITrackingRunLoopMode];到底干了什么呢

我们在Timer的block中打个断点,然后看下他的调用堆栈,看下他的执行流程

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000106c978bc 03-Runloop`__27-[ViewController timerDemo]_block_invoke(.block_descriptor=0x0000000106c9a0f0, timer=0x000060000002c780) at ViewController.m:50:38
    frame #1: 0x00007fff20871584 Foundation`__NSFireTimer + 67
    frame #2: 0x00007fff203a9112 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
    frame #3: 0x00007fff203a8be5 CoreFoundation`__CFRunLoopDoTimer + 926
    frame #4: 0x00007fff203a8198 CoreFoundation`__CFRunLoopDoTimers + 265
    frame #5: 0x00007fff203a2826 CoreFoundation`__CFRunLoopRun + 1949
    frame #6: 0x00007fff203a1b9e CoreFoundation`CFRunLoopRunSpecific + 567
    frame #7: 0x00007fff2b773db3 GraphicsServices`GSEventRunModal + 139
    frame #8: 0x00007fff24660af3 UIKitCore`-[UIApplication _run] + 912
    frame #9: 0x00007fff24665a04 UIKitCore`UIApplicationMain + 101
    frame #10: 0x0000000106c97d30 03-Runloop`main(argc=1, argv=0x00007ffee8f67be0) at main.m:15:16
    frame #11: 0x00007fff20257415 libdyld.dylib`start + 1
(lldb) 

可以看出调用流程是这样的:
CFRunLoopRunSpecific->__CFRunLoopRun->__CFRunLoopDoTimers->__CFRunLoopDoTimer-> __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

CFRunLoopRunSpecific
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
   if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
   result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
  if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
   return result;
}
  • __CFRunLoopDoObservers告诉RunLoop要开始run
  • __CFRunLoopRun
  • __CFRunLoopDoObservers告诉RunLoop 退出了
__CFRunLoopRun
static int32_t __CFRunLoopRun__CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
//  ....
do {
//通知observers即将处理timer事件
 if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//通知observers即将处理Sources事件
 if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//处理block
__CFRunLoopDoBlocks(rl, rlm);
//处理source0
 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
//处理sources0返回为YES
if (sourceHandledThisLoop) {
//处理block
            __CFRunLoopDoBlocks(rl, rlm);
    }
/// 判断有无端口消息(Source1)
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            /// 处理消息
            goto handle_msg;
        }
/// 通知 Observers: 即将进入休眠
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
__CFRunLoopSetSleeping(rl);
///等待被唤醒
 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
/// 通知 Observers: 被唤醒,结束休眠 (系统级别的通知实现)
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
...
 handle_msg:
 if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
///如果被timer唤醒
      CFRUNLOOP_WAKEUP_FOR_TIMER();
       __CFRunLoopDoTimers(rl, rlm, mach_absolute_time()
   }else if (livePort == dispatchPort) {
///如果被GCD唤醒
       CFRUNLOOP_WAKEUP_FOR_DISPATCH();
       __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
     }else {
//如果被source1处理
       CFRUNLOOP_WAKEUP_FOR_SOURCE();
        __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
    }
  }while (0 == retVal);
__CFRunLoopDoTimers
// rl and rlm are locked on entry and exit
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {    /* DOES CALLOUT */
    Boolean timerHandled = false;
    CFMutableArrayRef timers = NULL;
    for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
        
        if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
            if (rlt->_fireTSR <= limitTSR) {
                if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
                CFArrayAppendValue(timers, rlt);
            }
        }
    }
    
    for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
        Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
        timerHandled = timerHandled || did;
    }
    if (timers) CFRelease(timers);
    return timerHandled;
}
  • 通过循环遍历,然后判断__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)
  • CFArrayAppendValue(timers, rlt)timer添加到timers
  • 然后进行遍历,调用 __CFRunLoopDoTimer
__CFRunLoopDoTimer
static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) { 
     __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
}
  • __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__进行回调,行程闭环
    依次类推,Source,Observer ,Block 原理大同小异
    RunLoop 主要处理item 处理负责休眠的事物
如何创建一个常驻线程
  1. 为当前线程创建一个RunLoop
  2. 像当前RunLoop中添加一个port或者source来维持RunLoop事件循环
  3. 启动RunLoop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
总结

RunLoop原理:

    1. 通知observer即将进入loop
    1. 通知Observer,即将处理timer
    1. 通知Observer,即将处理Source0
    1. 处理Source0
    1. 如果有Source1,跳至第九步
    1. 通知Observer,线程即将休眠
    1. 休眠等待唤醒
    1. 通知Observer,线程被唤醒
    1. 处理唤醒时收到的信息,之后跳回2
    1. 通知Observer,即将退出loop
runtime运行逻辑

RunLoop的item (6大事件)

  • block调用 : __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
  • timer调用 :__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
  • 响应source0 :__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
  • 响应source1:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
  • GCD主队列:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
  • observer源:__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

source0和source1 的区别

source0: 用户点击等事件,按钮点击,触摸等事件,无法唤醒RunLoop
source1:系统端口信息,基于port,可以直接唤醒loop

** CFRunLoopModem五种模式**

kCFRunLoopDefaultMode:默认模式,主线程是在这个运行模式下运行

UITrackingRunLoopMode:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)

UIInitializationRunLoopMode:在刚启动App时第进入的第一个 Mode,启动完成后就不再使用

GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到

kCFRunLoopCommonModes:伪模式,不是一种真正的运行模式,是同步Source/Timer/Observer到多个Mode中的一种解决方案

    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容