029*:runloop:获取(CFRunLoopGetMain、CFRunLoopGetCurrent)创建(__CFRunLoopCreate __CFRunLoop结构体 )(CFRunloop、CFRunLoopMode、CFRunloopItem)(CFRunLoopRun -> CFRunLoopRunSpecific -> __CFRunLoopRun)

问题

 

0:Runloop生命周期

kCFRunLoopEntry -- 进入runloop循环
kCFRunLoopBeforeTimers -- 处理定时调用前回调
kCFRunLoopBeforeSources -- 处理input sources的事件
kCFRunLoopBeforeWaiting -- runloop睡眠前调用
kCFRunLoopAfterWaiting -- runloop唤醒后调用
kCFRunLoopExit -- 退出runloop

 

1:  runLoop与线程的关系
一一对应关系。由全局Runloop字典进行记录,其中key线程valuerunloop

2: runloop-》runloopModel-》item—》Source & Timer & Observer

获取(CFRunLoopGetMain、CFRunLoopGetCurrent)

创建(__CFRunLoopCreate  __CFRunLoop结构体 )

流程(CFRunloop、CFRunLoopMode、CFRunloopItem【source、timer、observer】)

执行(CFRunLoopRun -> CFRunLoopRunSpecific -> __CFRunLoopRun)

目录

1: 自动释放池的原理

2:RunLoop 源码分析

3:面试题

预备

自动释放池是OC中的一种内存自动回收机制,它可以将加入AutoreleasePool中的变量release的时机延迟,简单来说,就是当创建一个对象,在正常情况下,变量会在超出其作用域的时立即release。如果将对象加入到了自动释放池中,这个对象并不会立即释放,会等到runloop休眠/超出autoreleasepool作用域{}之后才会被释放。其机制如下图所示
  • 1、从程序启动到加载完成,主线程对应的runloop会处于休眠状态,等待用户交互来唤醒runloop

  • 2、用户的每一次交互都会启动一次runloop,用于处理用户的所有点击、触摸事件等

  • 3、runloop在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中

  • 4、在一次完整的runloop结束之前,会向自动释放池中所有对象发送release消息,然后销毁自动释放池

正文 

一:自动释放池的原理 

RunLoop事件接收和分发机制的一个实现,是线程相关的基础框架的一部分,一个RunLoop就是一个事件处理的循环,用来不停的调度工作以及处理输入事件。

RunLoop本质是一个 do-while循环,没事做就休息,来活了就干活。与普通的while循环是有区别的,普通的while循环会导致CPU进入忙等待状态,即一直消耗cpu,而RunLoop则不会,RunLoop是一种闲等待,即RunLoop具备休眠功能

RunLoop的作用

  • 保持程序的持续运行,让线程保持活力

  • 处理App中的各种事件(触摸、定时器、performSelector)

  • 节省cpu资源,提供程序的性能,该做事就做事,该休息就休息

 

 二:RunLoop 源码分析

RunLoop源码的下载地址,在其中找到最新版下载即可

1、RunLoop和线程的关系

一般在日常开发中,对于RunLoop的获取主要有以下两种方式

// 主运行循环
 CFRunLoopRef mainRunloop = CFRunLoopGetMain();
 // 当前运行循环
 CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();

2:进入CFRunLoopGetMain源码

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    //pthread_main_thread_np 主线程
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

2.1:进入_CFRunLoopGet0

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    //如果t不存在,则标记为主线程(即默认情况,默认是主线程)
    if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFSpinUnlock(&loopsLock);
        
        //创建全局字典,标记为kCFAllocatorSystemDefault
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        //通过主线程 创建主运行循环
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        //利用dict,进行key-value绑定操作,即可以说明,线程和runloop是一一对应的
        // dict : key value
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        
        CFRelease(mainLoop);
        __CFSpinLock(&loopsLock);
    }
    //通过其他线程获取runloop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    if (!loop) {
        //如果没有获取到,则新建一个运行循环
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            //将新建的runloop 与 线程进行key-value绑定
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFSpinUnlock(&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;
}

从这里可以说明,Runloop只有两种,一种是主线程的, 一个是其他线程的。即runloop和线程是一一对应的

总结:

  1. 主线程是static全局唯一第一次获取创建
  2. 线程不存在,默认使用主线程,并返回主线程的runloop
  3. 首次访问,会创建全局唯一__CFRunLoops字典key线程valuerunloop
    (线程runloop一一对应)
  4. 每次优先__CFRunLoops字典中,通过key(线程),获取value(runloop)。
  5. 如果runloop不存在,就创建线程对应的runloop,并更新__CFRunLoops字典对应值
  6. 更新TSD(线程私有存储),记录runloop
  7. 返回runloop

面试题: runLoop与线程的关系
一一对应关系。由全局Runloop字典进行记录,其中key线程valuerunloop

3、RunLoop的创建

3.1:进入__CFRunLoopCreate源码,其中主要是对runloop属性的赋值操作

static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
    CFRunLoopRef loop = NULL;
    CFRunLoopModeRef rlm;
    uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
    loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL);
    //如果loop为空,则直接返回NULL
    if (NULL == loop) {
        return NULL;
    }
    //runloop属性配置
    (void)__CFRunLoopPushPerRunData(loop);
    __CFRunLoopLockInit(&loop->_lock);
    loop->_wakeUpPort = __CFPortAllocate();
    if (CFPORT_NULL == loop->_wakeUpPort) HALT;
    __CFRunLoopSetIgnoreWakeUps(loop);
    loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
    loop->_commonModeItems = NULL;
    loop->_currentMode = NULL;
    loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    loop->_blocks_head = NULL;
    loop->_blocks_tail = NULL;
    loop->_counterpart = NULL;
    loop->_pthread = t;
#if DEPLOYMENT_TARGET_WINDOWS
    loop->_winthread = GetCurrentThreadId();
#else
    loop->_winthread = 0;
#endif
    rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
    if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
    return loop;
}

3.2:进入CFRunLoopRef的定义,根据定义得知,其实RunLoop也是一个对象。是__CFRunLoop结构体的指针类型

typedef struct __CFRunLoop * CFRunLoopRef;
👇
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;
    CFTypeRef _counterpart;
};

总结:

  1. __CFRunLoop为模板,创建Runloop结构体对象
  2. 属性初始化赋值
  3. Mode的获取:
    • 如果通过__kCFRunLoopModeTypeID读取到Modes,并且 Modes中存在kCFRunLoopDefaultMode,就直接返回找到的Mode
    • 否则,创建一个Mode,加入modes中。返回Mode

拓展:

  1. runLoop本质是__CFRunLoop格式的结构体

    记录线程锁port唤醒端口所在线程所有标记为Common的Model加入CommonMode的item事务当前Model所有Model


3.3:Mode类型
其中mode在苹果文档中提及的有五个,而在iOS中公开暴露出来的只有 
NSDefaultRunLoopMode和 NSRunLoopCommonModes。 NSRunLoopCommonModes实际上是一个 Mode 的集合,默认包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode
  • NSDefaultRunLoopMode默认的mode,正常情况下都是在这个mode

  • NSConnectionReplyMode

  • NSModalPanelRunLoopMode

  • NSEventTrackingRunLoopMode:使用这个Mode去跟踪来自用户交互的事件(比如UITableView上下滑动)

  • NSRunLoopCommonModes:伪模式,灵活性更好

Source & Timer & Observer

  • Source表示可以唤醒RunLoop的一些事件,例如用户点击了屏幕,就会创建一个RunLoop,主要分为Source0Source1

    • Source0表示 非系统事件,即用户自定义的事件

    • Source1表示系统事件,主要负责底层的通讯,具备唤醒能力

  • Timer就是常用NSTimer定时器这一类

  • Observer主要用于监听RunLoop的状态变化,并作出一定响应,主要有以下一些状态

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    //进入RunLoop
    kCFRunLoopEntry = (1UL << 0),
    //即将处理Timers
    kCFRunLoopBeforeTimers = (1UL << 1),
    //即将处理Source
    kCFRunLoopBeforeSources = (1UL << 2),
    //即将进入休眠
    kCFRunLoopBeforeWaiting = (1UL << 5),
    //被唤醒
    kCFRunLoopAfterWaiting = (1UL << 6),
    //退出RunLoop
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

RunLoop源码中查看Item类型,有以下几种

  • 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__

在这里以Timer为例,一般初始化timer时,都会将timer通过addTimer:forMode:方法添加到Runloop中,于是在源码中查找addTimer的相关方法,即CFRunLoopAddTimer方法,其源码实现如下,其实现主要判断是否是kCFRunLoopCommonModes,然后查找runloop的mode进行匹配处理

  • 其中kCFRunLoopCommonModes不是一种模式,是一种抽象的伪模式,比defaultMode更加灵活
  • 通过CFSetAddValue(rl->_commonModeItems, rlt);可以得知,runloopmode一对多的,同时可以得出mode与 item也是一对多
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
    __CFRunLoopLock(rl);
    
    // 重点 : kCFRunLoopCommonModes
    if (modeName == kCFRunLoopCommonModes) {
        //如果是kCFRunLoopCommonModes 类型
       
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        
        if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        //runloop与mode 是一对多的, mode与item也是一对多的
        CFSetAddValue(rl->_commonModeItems, rlt);
        if (NULL != set) {
            CFTypeRef context[2] = {rl, rlt};
            /* add new item to all common-modes */
            //执行
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            CFRelease(set);
        }
    } else {
        //如果是非commonMode类型
        //查找runloop的模型
        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
        if (NULL != rlm) {
            if (NULL == rlm->_timers) {
                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                cb.equal = NULL;
                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
            }
        }
        //判断mode是否匹配
        if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
            __CFRunLoopTimerLock(rlt);
            if (NULL == rlt->_runLoop) {
                rlt->_runLoop = rl;
            } else if (rl != rlt->_runLoop) {
                __CFRunLoopTimerUnlock(rlt);
                __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
                return;
            }
            // 如果匹配,则将runloop加进去,而runloop的执行依赖于  [runloop run]
            CFSetAddValue(rlt->_rlModes, rlm->_name);
            __CFRunLoopTimerUnlock(rlt);
            __CFRunLoopTimerFireTSRLock();
            __CFRepositionTimerInMode(rlm, rlt, false);
            __CFRunLoopTimerFireTSRUnlock();
            if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
                // Normally we don't do this on behalf of clients, but for
                // backwards compatibility due to the change in timer handling...
                if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
            }
        }
        if (NULL != rlm) {
            __CFRunLoopModeUnlock(rlm);
        }
    }
   
    __CFRunLoopUnlock(rl);
}

4: RunLoop执行

众所周知,RunLoop的执行依赖于run方法,从下面的堆栈信息中可以看出,其底层执行的是__CFRunLoopRun方法
进入__CFRunLoopRun源码,针对不同的对象,有不同的处理
  • 如果有observer,则调用 __CFRunLoopDoObservers

  • 如果有block,则调用__CFRunLoopDoBlocks

  • 如果有timer,则调用 __CFRunLoopDoTimers

  • 如果是source0,则调用__CFRunLoopDoSources0

  • 如果是source1,则调用__CFRunLoopDoSource1

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    ...
    
    do{
        ...
         //通知 Observers: 即将处理timer事件
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        //通知 Observers: 即将处理Source事件
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        //处理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        //处理sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        //处理sources0返回为YES
        if (sourceHandledThisLoop) {
            // 处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        ...
        
        //如果是timer
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer, because we apparently fired early
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
        
        ...
        
        //如果是source1
        CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
        if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            mach_msg_header_t *reply = NULL;
            sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
            if (NULL != reply) {
                (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
            }
#elif DEPLOYMENT_TARGET_WINDOWS
            sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
        }
        ...
    
    }while (0 == retVal);
    
    ...
}

进入__CFRunLoopDoTimers源码,主要是通过for循环,对单个timer进行处理

static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {    /* DOES CALLOUT */
    ...
    //循环遍历,做下层单个timer的执行
    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;
    }
    ...
}
进入__CFRunLoopDoTimer源码,主要逻辑是timer执行完毕后,会主动调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__函数,正好与timer堆栈调用中的一致
// mode and rl are locked on entry and exit
static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) { 
    ...
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
    ...
}

timer执行总结

  • 为自定义的timer,设置Mode,并将其加入RunLoop

  • 在RunLoop的run方法执行时,会调用__CFRunLoopDoTimers执行所有timer

  • __CFRunLoopDoTimers方法中,会通过for循环执行单个timer的操作

  • __CFRunLoopDoTimer方法中,timer执行完毕后,会执行对应的timer回调函数

以上,是针对timer的执行分析,对于observer、block、source0、source1,其执行原理与timer是类似的,这里就不再重复说明以下是苹果官方文档针对RunLoop处理不同源的图示

5、RunLoop 底层原理

从上述的堆栈信息中可以看出,run在底层的实现路径为CFRunLoopRun -> CFRunLoopRunSpecific -> __CFRunLoopRun

1:CFRunLoopRun

进入CFRunLoopRun源码,其中传入的参数1.0e10(科学计数) 等于 1* e^10,用于表示超时时间

void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        // 1.0e10 : 科学技术 1*10^10
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

2:进入CFRunLoopRunSpecific源码,,首先根据modeName找到对应的mode,然后主要分为三种情况:

    • 如果是entry,则通知observer,即将进入runloop

    • 如果是exit,则通过observer,即将退出runloop

    • 如果是其他中间状态,主要是通过runloop处理各种源

其伪代码表示如下

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;
    
}

3:进入__CFRunLoopRun源码,由于这部分代码较多,于是这里用伪代码代替。其主要逻辑是根据不同的事件源进行不同的处理,当RunLoop休眠时,可以通过相应的事件唤醒RunLoop

//核心函数
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode){
    
    //通过GCD开启一个定时器,然后开始跑圈
    dispatch_source_t timeout_timer = NULL;
    ...
    dispatch_resume(timeout_timer);
    
    int32_t retVal = 0;
    
    //处理事务,即处理items
    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 (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
            // 处理消息
            goto handle_msg;
        }
        
        
        // 通知 Observers: 即将进入休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        
        // 等待被唤醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
        
        // 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;
}

 

run流程如下图:
看完源码后,runloop运行周期唤醒方式十分清晰了。现在奉上经典Runloop流程图
 

补充说明:

  1. 【最外层流程】
    kCFRunLoopEntry进入循环 (发通知)
    -> __CFRunLoopRun运行循环
    -> kCFRunLoopExit退出循环(发通知)

  2. 【循环内部】
    kCFRunLoopBeforeTimers即将处理Timer(发通知)
    -> kCFRunLoopBeforeSources即将处理Sources0(发通知)
    __CFRunLoopDoBlocks处理Blocks)
    -> __CFRunLoopDoSources0处理Sources0
    __CFRunLoopDoBlocks处理Blocks)
    -> __CFRunLoopServiceMachPort: 监听Port端口消息(source1),有消息就跳转handle_msg
    -> kCFRunLoopBeforeWaiting: 将进入休眠 (发通知)
    进入休眠,等待唤醒(内部的Timer到期、gcd都可唤醒)
    -> 线程被唤醒, (发通知)

3.【handle_msg】处理消息:

  • 被Timers唤醒(CFRUNLOOP_WAKEUP_FOR_TIMER): __CFRunLoopDoTimers(发通知)
  • 被gcd唤醒(CFRUNLOOP_WAKEUP_FOR_DISPATCH):__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(发通知)
  • 被source唤醒(CFRUNLOOP_WAKEUP_FOR_SOURCE): __CFRunLoopDoSource1
    __CFRunLoopDoBlocks处理Blocks)
  • 检查stopfinish
Timer、dispatch、source等回调函数:
// main  dispatch queue
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__

// __CFRunLoopDoObservers
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

// __CFRunLoopDoBlocks
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__

// __CFRunLoopDoSources0
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__

// __CFRunLoopDoSource1
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__

// __CFRunLoopDoTimers
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

在执行回调Block前,我们可以在堆栈中看到上述回调函数

回调函数检验:
(每次触发TouchBegin时,所在线程runloop都会调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__函数)

三:面试题

AutoreleasePool 相关

面试题1:临时变量什么时候释放?

  • 如果在正常情况下,一般是超出其作用域就会立即释放

  • 如果将临时变量加入了自动释放池,会延迟释放,即在runloop休眠或者autoreleasepool作用域之后释放

面试题2:AutoreleasePool原理

  • 自动释放池的本质是一个AutoreleasePoolPage结构体对象,是一个栈结构存储的页,每一个AutoreleasePoolPage都是以双向链表的形式连接

  • 自动释放池的压栈出栈主要是通过结构体的构造函数和析构函数调用底层的objc_autoreleasePoolPudhobjc_autoreleasePoolPop,实际上是调用AutoreleasePoolPagepushpop两个方法

  • 每次调用push操作其实就是创建一个新的AutoreleasePoolPage,而AutoreleasePoolPage的具体操作就是插入一个POOL_BOUNDARY,并返回插入POOL_BOUNDARY的内存地址。而push内部调用autoreleaseFast方法处理,主要有以下三种情况

    • page存在,且不满时,调用add方法将对象添加至page的next指针处,并next递增

    • page存在,且已满时,调用autoreleaseFullPage初始化一个新的page,然后调用add方法将对象添加至page栈中

    • page不存在时,调用autoreleaseNoPage创建一个hotPage,然后调用add方法将对象添加至page栈中

  • 当执行pop操作时,会传入一个值,这个值就是push操作的返回值,即POOL_BOUNDARY的内存地址token。所以pop内部的实现就是根据token找到哨兵对象所处的page中,然后使用 objc_release释放 token之前的对象,并把next指针到正确位置

面试题3:AutoreleasePool能否嵌套使用?

  • 可以嵌套使用,其目的是可以控制应用程序的内存峰值,使其不要太高

  • 可以嵌套的原因是因为自动释放池是以栈为节点,通过双向链表的形式连接的,且是和线程一一对应的

  • 自动释放池的多层嵌套其实就是不停的pushs哨兵对象,在pop时,会先释放里面的,在释放外面的

面试题4:哪些对象可以加入AutoreleasePool?alloc创建可以吗?

  • 使用new、alloc、copy关键字生成的对象和retain了的对象需要手动释放,不会被添加到自动释放池中

  • 设置为autorelease的对象不需要手动释放,会直接进入自动释放池

  • 所有 autorelease 的对象,在出了作用域之后,会被自动添加到最近创建的自动释放池中

面试题5:AutoreleasePool的释放时机是什么时候?

  • App 启动后,苹果在主线程 RunLoop里注册了两个 Observer,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()

  • 第一个 Observer 监视的事件是 Entry(即将进入 Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是 -2147483647,优先级最高,保证创 建释放池发生在其他所有回调之前。

  • 第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用 _objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即 将退出 Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

面试题6:thread 和 AutoreleasePool的关系

官方文档中,找到如下说明

Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects (see Threads). As new pools are created, they get added to the top of the stack. When pools are deallocated, they are removed from the stack. Autoreleased objects are placed into the top autorelease pool for the current thread. When a thread terminates, it automatically drains all of the autorelease pools associated with itself.

大致意思如下:

  • 每个线程,包括主线程在内都维护了自己的自动释放池堆栈结构

  • 新的自动释放池在被创建时,会被添加到栈顶;当自动释放池销毁时,会从栈中移除

  • 对于当前线程来说,会将自动释放的对象放入自动释放池的栈顶;在线程停止时,会自动释放掉与该线程关联的所有自动释放池

总结:每个线程都有与之关联的自动释放池堆栈结构,新的pool在创建时会被压栈到栈顶,pool销毁时,会被出栈,对于当前线程来说,释放对象会被压栈到栈顶,线程停止时,会自动释放与之关联的自动释放池

面试题7:RunLoop 和 AutoreleasePool的关系
官方文档中,找到如下说明

The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.

大致意思如下:

  • 主程序的RunLoop在每次事件循环之前之前,会自动创建一个 autoreleasePool

  • 并且会在事件循环结束时,执行drain操作,释放其中的对象

RunLoop相关

面试题1

当前有个子线程,子线程中有个timer。timer是否能够执行 并进行持续的打印?

 CJLThread *thread = [[CJLThread alloc] initWithBlock:^{

        // thread.name = nil 因为这个变量只是捕捉
        // CJLThread *thread = nil
        // thread = 初始化 捕捉一个nil进来
        NSLog(@"%@---%@",[NSThread currentThread],[[NSThread currentThread] name]);
        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"hello word");            // 退出线程--结果runloop也停止了
            if (self.isStopping) {
                [NSThread exit];
            }
        }];
    }];

    thread.name = @"lgcode.com";
    [thread start];
  • 不可以,因为子线程的runloop默认不启动, 需要runloop run启动,需要将上述代码改成下面这样:
//改成
 CJLThread *thread = [[CJLThread alloc] initWithBlock:^{

    // thread.name = nil 因为这个变量只是捕捉
    // CJLThread *thread = nil
    // thread = 初始化 捕捉一个nil进来
    NSLog(@"%@---%@",[NSThread currentThread],[[NSThread currentThread] name]);
    [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"hello word");            // 退出线程--结果runloop也停止了
        if (self.isStopping) {
            [NSThread exit];
        }
    }];
     [[NSRunLoop currentRunLoop] run];
}];

thread.name = @"lgcode.com";
[thread start];

面试题2:RunLoop和线程的关系

  • 每个线程都有一个与之对应的RunLoop,所以RunLoop与线程是一一对应的,其绑定关系通过一个全局的DIctionary存储,线程为key,runloop为value。

  • 线程中的RunLoop主要是用来管理线程的,当线程的RunLoop开启后,会在执行完任务后进行休眠状态,当有事件触发唤醒时,又开始工作,即有活时干活,没活就休息

  • 主线程RunLoop默认开启的,在程序启动之后,会一直运行,不会退出

  • 其他线程的RunLoop默认是不开启的,如果需要,则手动开启

面试3:NSRunLoop 和 CFRunLoopRef 区别

  • NSRunLoop是基于CFRunLoopRef面向对象的API,是不安全

  • CFRunLoopRef是基于C语言,是线程安全

面试4:Runloop的mode作用是什么?

mode主要是用于指定RunLoop中事件优先级的

面试5:以+scheduledTimerWithTimeInterval:的方式触发的timer,在滑动页面上的列表时,timer会暂停回调, 为什么?如何解决?

  • timer停止的原因是因为滑动scrollView时,主线程的RunLoop会从NSDefaultRunLoopMode切换到UITrackingRunLoopMode,而timer是添加在NSDefaultRunLoopMode。所以timer不会执行

  • timer放入NSRunLoopCommonModes中执行

 

注意

Runloop使用场景

1:类似 AFNetworking中的RunLoop案例:常驻子线程。

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];    
    // 1.测试线程的销毁
    [self threadTest];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self performSelector:@selector(subThreadOpetion) onThread:self.subThread withObject:nil waitUntilDone:NO];
}

- (void)threadTest
{
    HLThread *subThread = [[HLThread alloc] initWithTarget:self selector:@selector(subThreadEntryPoint) object:nil];
    [subThread setName:@"HLThread"];
    [subThread start];    self.subThread = subThread;
}/**
 子线程启动后,启动runloop
 */- (void)subThreadEntryPoint
{    @autoreleasepool {        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];        //如果注释了下面这一行,子线程中的任务并不能正常执行
        [runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];      
     NSLog(@"启动RunLoop前--%@",runLoop.currentMode); [runLoop run]; } }/** 子线程任务 */- (void)subThreadOpetion { NSLog(@"启动RunLoop后--%@",[NSRunLoop currentRunLoop].currentMode);
    NSLog(@"%@----子线程任务开始",[NSThread currentThread]); [NSThread sleepForTimeInterval:3.0];
    NSLog(@"%@----子线程任务结束",[NSThread currentThread]); } @end

2:NSTimer子线程

// 第一种写法
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[timer fire];
// 第二种写法
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES];

上面的两种写法其实是等价的。第二种写法,默认也是将timer添加到NSDefaultRunLoopMode下的,并且会自动fire。。

我们在子线程中使用timer,也可以解决上面的问题,但是需要注意的是把timer加入到当前runloop后,必须让runloop 运行起来,否则timer仅执行一次。

//首先是创建一个子线程
- (void)createThread
{
    NSThread *subThread = [[NSThread alloc] initWithTarget:self selector:@selector(timerTest) object:nil];
    [subThread start];
    self.subThread = subThread;
}

// 创建timer,并添加到runloop的mode中
- (void)timerTest
{
    @autoreleasepool {
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        NSLog(@"启动RunLoop前--%@",runLoop.currentMode);
        NSLog(@"currentRunLoop:%@",[NSRunLoop currentRunLoop]);
        // 第一种写法,改正前
    //    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES];
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    //    [timer fire];
        // 第二种写法
        [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES];
    
        [[NSRunLoop currentRunLoop] run];
    }
}

//更新label
- (void)timerUpdate
{
    NSLog(@"当前线程:%@",[NSThread currentThread]);
    NSLog(@"启动RunLoop后--%@",[NSRunLoop currentRunLoop].currentMode);
    NSLog(@"currentRunLoop:%@",[NSRunLoop currentRunLoop]);
    dispatch_async(dispatch_get_main_queue(), ^{
        self.count ++;
        NSString *timerText = [NSString stringWithFormat:@"计时器:%ld",self.count];
        self.timerLabel.text = timerText;
    });
}

3:tableview中的timer

- (void)timerTest
{
    // 第一种写法
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    [timer fire];
    // 第二种写法,因为是固定添加到defaultMode中,就不要用了
}

4:有一个非常好的关于设置图片视图的图片,在RunLoop切换Mode时优化的例子:RunLoopWorkDistribution

引用

1:iOS-底层原理 33:内存管理(三)AutoReleasePool & NSRunLoop 底层分析

2:OC底层原理三十七:内存管理(autorelease & runloop)

3:iOS 底层原理 - 自动释放池 

posted on 2020-12-04 20:19  风zk  阅读(266)  评论(0编辑  收藏  举报

导航