iOS Runloop底層詳解

一 什么是Runloop
二 Runloop的運行邏輯
三 Runloop在實際開發(fā)中的應(yīng)用
一 什么是Runloop
1.1 Runloop的基本認識

RunLoop是一個接收處理異步消息事件的循環(huán)悄晃,一個循環(huán)中:等待事件發(fā)生最蕾,然后將這個事件送到能處理它的地方辈双。


runloop
1.1.1 它的定義有兩個點:

1 它是一個運行循環(huán)赎懦,like while(1),它是保證程序一直運行不退出的基本條件
2 在這個循環(huán)里面有事情時它就會處理,沒事情時它就休息累魔,既保證了一些有些操作它能及時相應(yīng)屎飘,又保證了沒活干時不浪費系統(tǒng)資源。

1.1.2 那么Runloop它在app中都有哪些作用秒裕?

1 保持程序的持續(xù)運行
2 事件響應(yīng)袱蚓、手勢識別、界面刷新
3 AutoreleasePool自動釋放池
4 定時器(Timer)几蜻、PerformSelector 定時器喇潘,延遲執(zhí)行操作
5 GCD Async Main Queue 線程主隊列里的操作
6節(jié)省CPU資源,提高程序性能:該做事時做事梭稚,該休息時休息

1.2 runloop底層相關(guān)對象介紹

iOS中有2套API來訪問和使用RunLoop
Foundation:NSRunLoop
Core Foundation:CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表著RunLoop對象
NSRunLoop是基于CFRunLoopRef的一層OC包裝

CFRunLoopRef是開源的
https://opensource.apple.com/tarballs/CF/
Core Foundation中關(guān)于RunLoop的有5個類

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef
    下面我們來逐一介紹
1.2.1 CFRunLoopRef RunLoop對象
CFRunLoop.h
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;  //通用模式標(biāo)記的任務(wù)
    CFRunLoopModeRef _currentMode;   //當(dāng)前模式
    CFMutableSetRef _modes;        //所有模式列表
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

下面我們介紹一下RunLoop對象里幾個主要的字段:

1.2.2 pthread_t _pthread

線程對象颖低,一個runloop對象里面就有一個線程對象,RunLoop是來管理線程的,當(dāng)線程的RunLoop被開啟后弧烤,線程會在執(zhí)行完任務(wù)后進入休眠狀態(tài)忱屑,有了任務(wù)就會被喚醒去執(zhí)行任務(wù).稍后我們再詳細講解線程方面的東西

1.2.3 CFMutableSetRef _commonModes

通用模式,首先什么是模式,就是一種運行環(huán)境莺戒,就像聽歌伴嗡,有單曲模式,循環(huán)模式从铲,隨機模式瘪校,runloop也有運行模式,界面靜止處于一種模式名段,界面滑動又處于另一種模式阱扬,模式之間相互獨立互不影響,runloop 同時只能在一種模式下運行伸辟,這就保證了界面滾動滑動時麻惶,就處于一種滑動模式,專門干這件事信夫,其他啥也不干窃蹋,界面就會滑動很流暢。

1.2.4 CFMutableSetRef _commonModeItems;

如果一個任務(wù)運行在_commonModes下運行那么它就會被加入到_commonModeItems里面比如一個定時器為了保證滑動不卡住静稻,我們會讓它在UITrackingRunLoopMode也能運行脐彩,這就需要它在_commonModes下運行,那么這個定時器就會被加入到_commonModeItems里姊扔,runloop在處理這個定時器任務(wù)時,它就會從_commonModeItems里找出這個任務(wù)去執(zhí)行

1.2.5 CFRunLoopModeRef _currentMode

runloop當(dāng)前所處的模式

1.2.6 CFMutableSetRef _modes

RunLoop所有運行模式列表梅誓,它主要有以下5個:
kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
UITrackingRunLoopMode:界面跟蹤Mode,用于ScrollView追蹤觸摸滑動恰梢,保證界面滑動時不受其他Mode影響
kCFRunLoopCommonModes:這是一個占位用的Mode,不是一種真正的Mode梗掰,它是kCFRunLoopDefaultMode,UITrackingRunLoopMode 并集的標(biāo)識嵌言。

UIInitializationRunLoopMode:在剛啟動App時進入的第一個Mode,啟動完成后不再使用
GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部Mode及穗,通常用不到
下面讓我們來看看具體的模式是什么樣的

1.2.7 RunLoop通用模式有兩種:

1 KCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默認Mode摧茴,通常主線程是在這個Mode下運行
2 UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動埂陆,保證界面滑動時不受其他 Mode 影響
接下來讓我們看看模式對象里面都有些什么

CFRunLoopModeRef
CFRunLoop.c
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
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;  // 事件源0
    CFMutableSetRef _sources1; // 事件源1
    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 */
};

一個RunLoop包含若干個Mode苛白,每個Mode又包含若干個Source0/Source1/Timer/Observer下面我們解釋下這幾個字段具體作用
1 Source0
觸摸事件處理
performSelector:onThread:

2 Source1
基于Port的線程間通信
系統(tǒng)事件捕捉

3 Timers
NSTimer
performSelector:withObject:afterDelay:

4 Observers
用于監(jiān)聽RunLoop的狀態(tài)
UI刷新(BeforeWaiting)
Autorelease pool(BeforeWaiting)
下面我們總結(jié)下模式結(jié)構(gòu),如圖:


模式結(jié)構(gòu)圖
  • 1 RunLoop啟動時只能選擇其中一個Mode焚虱,作為currentMode
  • 2 如果需要切換Mode购裙,只能退出當(dāng)前Loop,再重新選擇一個Mode進入
  • 3 不同組的Source0/Source1/Timer/Observer能分隔開來鹃栽,互不影響
  • 4 如果Mode里沒有任何Source0/Source1/Timer/Observer躏率,RunLoop會立馬退出
1.3 CFRunLoopSourceRef
runloop.c

typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
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;
};

事件源對象
1 Source0
觸摸事件處理,點擊滑動等
performSelector:onThread: // 在指定一個線程執(zhí)行任務(wù)

2 Source1
基于Port的線程間通信
系統(tǒng)事件捕捉

1.4 CFRunLoopTimerRef
runloop.c

__CFRunLoopTimer * CFRunLoopTimerRef;
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 */
};

定時器,就是定時執(zhí)行一個任務(wù)薇芝,也是在runloop中處理的

1.5 CFRunLoopObserverRef
runloop.c
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
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 */
};

CFRunLoopObserverRef是觀察者蓬抄,能夠監(jiān)聽RunLoop的狀態(tài)改變,如下:

CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
      
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"即將進入runloop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即將處理 Timer");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即將處理 Sources");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即將進入休眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"從休眠中喚醒loop");
                break;
            case kCFRunLoopExit:
                NSLog(@"即將退出runloop");
                break;
                
            default:
                break;
        }
        
    });
  CFRunLoopAddObserver(CFRunLoopGetCurrent(),observer,kCFRunLoopDefaultMode);
    
    /// 這里可以自己寫一個NSTimer實驗一下
    
    // 釋放runloop
    CFRelease(observer);

runloop 狀態(tài)主要又如下幾種:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),              //即將進入Loop
    kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer
    kCFRunLoopBeforeSources = (1UL << 2),//即將處理source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),    //剛從休眠中喚醒
    kCFRunLoopExit = (1UL << 7),                 //即將退出Loop
    kCFRunLoopAllActivities = 0x0FFFFFFFU// 包含所有以上狀態(tài)
};

以上就是RunLoop相關(guān)對象的介紹夯到,下面我們來講一下RunLoop的運行邏輯嚷缭。

二 Runloop的運行邏輯

官網(wǎng)上有一張圖是這樣的:


RunLoop

這張圖講了RunLoop 循環(huán)處理source0 source1 timer事件,他并沒有講清楚具體的執(zhí)行步驟黄娘,以及線程休眠的細節(jié)峭状,接下來看一張詳細的執(zhí)行圖:


RunLoop運行邏輯圖

它的具體執(zhí)行步驟如下:
01、通知Observers:進入Loop
02逼争、通知Observers:即將處理Timers
03优床、通知Observers:即將處理Sources
04、處理Blocks
05誓焦、處理Source0(可能會再次處理Blocks)
06胆敞、如果存在Source1,就跳轉(zhuǎn)到第8步
07杂伟、通知Observers:開始休眠(等待消息喚醒)
08移层、通知Observers:結(jié)束休眠(被某個消息喚醒)
01> 處理Timer
02> 處理GCD Async To Main Queue
03> 處理Source1
09、處理Blocks
10赫粥、根據(jù)前面的執(zhí)行結(jié)果观话,決定如何操作
01> 回到第02步
02> 退出Loop
11、通知Observers:退出Loop
下面讓我們看一下越平,簡化的源碼執(zhí)行流程

// 共外部調(diào)用的公開的CFRunLoopRun方法频蛔,其內(nèi)部會調(diào)用CFRunLoopRunSpecific
void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

// 經(jīng)過精簡的 CFRunLoopRunSpecific 函數(shù)代碼,其內(nèi)部會調(diào)用__CFRunLoopRun函數(shù)
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */

    // 通知Observers : 進入Loop
    // __CFRunLoopDoObservers內(nèi)部會調(diào)用 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
函數(shù)
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    // 核心的Loop邏輯
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    // 通知Observers : 退出Loop
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    return result;
}

// 精簡后的 __CFRunLoopRun函數(shù)秦叛,保留了主要代碼
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    int32_t retVal = 0;
    do {
        // 通知Observers:即將處理Timers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); 
        
        // 通知Observers:即將處理Sources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        // 處理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 處理Sources0
        if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
            // 處理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        // 如果有Sources1晦溪,就跳轉(zhuǎn)到handle_msg標(biāo)記處
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            goto handle_msg;
        }
        
        // 通知Observers:即將休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        
        // 進入休眠,等待其他消息喚醒
        __CFRunLoopSetSleeping(rl);
        __CFPortSetInsert(dispatchPort, waitSet);
        do {
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        } while (1);
        
        // 醒來
        __CFPortSetRemove(dispatchPort, waitSet);
        __CFRunLoopUnsetSleeping(rl);
        
        // 通知Observers:已經(jīng)喚醒
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg: // 看看是誰喚醒了RunLoop挣跋,進行相應(yīng)的處理
        if (被Timer喚醒的) {
            // 處理Timer
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        }
        else if (被GCD喚醒的) {
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else { // 被Sources1喚醒的
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
        }
        
        // 執(zhí)行Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 根據(jù)之前的執(zhí)行結(jié)果三圆,來決定怎么做,為retVal賦相應(yīng)的值
        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;
}

這里面有幾個細節(jié)我們講一下:

1 線程休眠

Runloop的本質(zhì)就是一個do,while循環(huán)避咆,當(dāng)有事做時做事舟肉,沒事做時休眠。怎么休眠查库,這個是由系統(tǒng)內(nèi)核來調(diào)度的度气,是真的休眠了cup不占用資源了。

  __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy) {
    Boolean originalBuffer = true;
    kern_return_t ret = KERN_SUCCESS;
    for (;;) {      /* In that sleep of death what nightmares may come ... */
        mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
        msg->msgh_bits = 0;
        msg->msgh_local_port = port;
        msg->msgh_remote_port = MACH_PORT_NULL;
        msg->msgh_size = buffer_size;
        msg->msgh_id = 0;
        if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
        ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);

        // Take care of all voucher-related work right after mach_msg.
        // If we don't release the previous voucher we're going to leak it.
        voucher_mach_msg_revert(*voucherState);
        
        // Someone will be responsible for calling voucher_mach_msg_revert. This call makes the received voucher the current one.
        *voucherState = voucher_mach_msg_adopt(msg);
        
        if (voucherCopy) {
            if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) {
                // Caller requested a copy of the voucher at this point. By doing this right next to mach_msg we make sure that no voucher has been set in between the return of mach_msg and the use of the voucher copy.
                // CFMachPortBoost uses the voucher to drop importance explicitly. However, we want to make sure we only drop importance for a new voucher (not unchanged), so we only set the TSD when the voucher is not state_unchanged.
                *voucherCopy = voucher_copy();
            } else {
                *voucherCopy = NULL;
            }
        }

        CFRUNLOOP_WAKEUP(ret);
        if (MACH_MSG_SUCCESS == ret) {
            *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
            return true;
        }
        if (MACH_RCV_TIMED_OUT == ret) {
            if (!originalBuffer) free(msg);
            *buffer = NULL;
            *livePort = MACH_PORT_NULL;
            return false;
        }
        if (MACH_RCV_TOO_LARGE != ret) break;
        buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
        if (originalBuffer) *buffer = NULL;
        originalBuffer = false;
        *buffer = realloc(*buffer, buffer_size);
    }
    HALT;
    return false;
}
  ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);
2 runloop和線程的關(guān)系

1 每條線程都有唯一的一個與之對應(yīng)的RunLoop對象
2 RunLoop保存在一個全局的Dictionary里膨报,線程作為key磷籍,RunLoop作為value
3 線程剛創(chuàng)建時并沒有RunLoop對象适荣,RunLoop會在第一次獲取它時創(chuàng)建

     NSRunLoop *runloop = [NSRunLoop currentRunLoop];
//    CFRunLoopRef runloop2 = CFRunLoopGetCurrent();

4 RunLoop會在線程結(jié)束時銷毀
5 主線程的RunLoop已經(jīng)自動獲取(創(chuàng)建)院领,子線程默認沒有開啟RunLoop

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));   //里面會創(chuàng)建主線程弛矛,并開啟RunLoop
    }
}

我們講完了runloop具體執(zhí)行流程,那么在平時開發(fā)中有哪些應(yīng)用呢比然,我們簡單的來看一下

三 Runloop在實際開發(fā)中的應(yīng)用
  • 控制線程生命周期(線程闭擅ィ活)
  • 解決NSTimer在滑動時停止工作的問題
  • 監(jiān)控應(yīng)用卡頓
  • 性能優(yōu)化
    下面我們簡單的舉兩個例子
3.1 控制線程生命周期(線程保活)
#import <Foundation/Foundation.h>

typedef void (^MyPermenantThreadTask)(void);

@interface MyPermenantThread : NSObject

/**
 開啟線程
 */
- (void)run;

/**
 在當(dāng)前子線程執(zhí)行一個任務(wù)
 */
- (void)executeTask:(MyPermenantThreadTask)task;

/**
 結(jié)束線程
 */
- (void)stop;

@end


#import "MyPermenantThread.h"

/** MJThread **/
@interface MyThread : NSThread
@end
@implementation MyThread
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

/** MJPermenantThread **/
@interface MyPermenantThread()
@property (strong, nonatomic) MyThread *innerThread;
@property (assign, nonatomic, getter=isStopped) BOOL stopped;
@end

@implementation MyPermenantThread
#pragma mark - public methods
- (instancetype)init
{
    if (self = [super init]) {
        self.stopped = NO;
        
        __weak typeof(self) weakSelf = self;
        
        self.innerThread = [[MyThread alloc] initWithBlock:^{
            [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; //如果Mode里沒有任何Source0/Source1/Timer/Observer强法,RunLoop會立馬退出 所以為了不讓它退出我們加了個source1 你也可以加其他的
            
            while (weakSelf && !weakSelf.isStopped) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
        }];
        
        [self.innerThread start];
    }
    return self;
}

- (void)run
{
    if (!self.innerThread) return;

    [self.innerThread start];
}

- (void)executeTask:(MyPermenantThreadTask)task
{
    if (!self.innerThread || !task) return;
    
    [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}

- (void)stop
{
    if (!self.innerThread) return;
    
    [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    
    [self stop];
}

#pragma mark - private methods
- (void)__stop
{
    self.stopped = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.innerThread = nil;
}

- (void)__executeTask:(MyPermenantThreadTask)task
{
    task();
}

@end


#import "ViewController.h"
#import "MyPermenantThread.h"

@interface ViewController ()
@property (strong, nonatomic) MJPermenantThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.thread = [[MJPermenantThread alloc] init];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self.thread executeTask:^{
        NSLog(@"執(zhí)行任務(wù) - %@", [NSThread currentThread]);
    }];
}

- (IBAction)stop {
    [self.thread stop];
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
}

@end

我們利用RunLoop的線程休眠機制實現(xiàn)了一個沒任務(wù)就會休眠的線程MyPermenantThread万俗, 只要你不調(diào)用stop 你就可以重復(fù)的使用這個線程來執(zhí)行其他任務(wù),很適合做大量串行執(zhí)行的子任務(wù)饮怯,比如說有依賴的大量網(wǎng)絡(luò)請求等闰歪。

3.2 解決NSTimer在滑動時停止工作的問題
  static int number = 0;
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%d", ++number);
    }];
     [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

通常我們寫一個timer

 [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%d", ++count);
    }];

這個時候timer默認處于NSDefaultRunLoopMode模式當(dāng)我們滑動頁面的時候RunLoop 會切換到UITrackingRunLoopMode模式,這樣我們的timer就停止工作了蓖墅,就像商城秒殺倒計時库倘,滑動頁面時倒計時就停止了,為了解決這個問題我們要讓timer在UITrackingRunLoopMode下也能工作论矾,所以我們給它指定了NSRunLoopCommonModes模式教翩,這個模式標(biāo)記這個timer可以在NSDefaultRunLoopMode、UITrackingRunLoopMode模式下運行贪壳。

3.3 監(jiān)控應(yīng)用卡頓

通過對 RunLoop 原理的分析饱亿,我們可以看出在整個過程中,loop 的狀態(tài)包括 6 個闰靴,其代碼定義 如下:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {

   kCFRunLoopEntry , // 進入 loop

   kCFRunLoopBeforeTimers , // 觸發(fā) Timer 回調(diào)

   kCFRunLoopBeforeSources , // 觸發(fā) Source0 回調(diào)

   kCFRunLoopBeforeWaiting , // 等待 mach_port 消息

   kCFRunLoopAfterWaiting ), // 接收 mach_port 消息

   kCFRunLoopExit , // 退出 loop

   kCFRunLoopAllActivities  // loop 所有狀態(tài)改變

}

如果 RunLoop 的線程路捧,進入睡眠前方法的執(zhí)行時間過長而導(dǎo)致無法進入睡眠,或者線程喚醒后 接收消息時間過長而無法進入下一步的話传黄,就可以認為是線程受阻了。如果這個線程是主線程的話队寇,表現(xiàn)出來的就是出現(xiàn)了卡頓膘掰。
所以,如果我們要利用 RunLoop 原理來監(jiān)控卡頓的話佳遣,就是要關(guān)注這兩個階段识埋。RunLoop 在進 入睡眠之前和喚醒后的兩個 loop 狀態(tài)定義的值分別是 kCFRunLoopBeforeSources 和
kCFRunLoopAfterWaiting ,也就是要觸發(fā) Source0 回調(diào)和接收 mach_port 消息兩個狀態(tài)零渐。
具體實現(xiàn)代碼如下:
實現(xiàn)代碼

好了我就講這個兩個例子至于監(jiān)控應(yīng)用卡頓窒舟,性能優(yōu)化的應(yīng)用,網(wǎng)上資料很多的诵盼,可自行查閱惠豺。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末银还,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子洁墙,更是在濱河造成了極大的恐慌蛹疯,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件热监,死亡現(xiàn)場離奇詭異捺弦,居然都是意外死亡,警方通過查閱死者的電腦和手機孝扛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門列吼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人苦始,你說我怎么就攤上這事寞钥。” “怎么了盈简?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵凑耻,是天一觀的道長。 經(jīng)常有香客問我柠贤,道長香浩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任臼勉,我火速辦了婚禮邻吭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宴霸。我一直安慰自己囱晴,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布瓢谢。 她就那樣靜靜地躺著畸写,像睡著了一般。 火紅的嫁衣襯著肌膚如雪氓扛。 梳的紋絲不亂的頭發(fā)上枯芬,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機與錄音采郎,去河邊找鬼千所。 笑死,一個胖子當(dāng)著我的面吹牛蒜埋,可吹牛的內(nèi)容都是我干的淫痰。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼整份,長吁一口氣:“原來是場噩夢啊……” “哼待错!你這毒婦竟也來了籽孙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤朗鸠,失蹤者是張志新(化名)和其女友劉穎蚯撩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烛占,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡胎挎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了忆家。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片犹菇。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖芽卿,靈堂內(nèi)的尸體忽然破棺而出揭芍,到底是詐尸還是另有隱情,我是刑警寧澤卸例,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布称杨,位于F島的核電站,受9級特大地震影響筷转,放射性物質(zhì)發(fā)生泄漏姑原。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一呜舒、第九天 我趴在偏房一處隱蔽的房頂上張望锭汛。 院中可真熱鬧,春花似錦袭蝗、人聲如沸唤殴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朵逝。三九已至,卻和暖如春乡范,著一層夾襖步出監(jiān)牢的瞬間配名,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工篓足, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人闰蚕。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓栈拖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親没陡。 傳聞我的和親對象是個殘疾皇子涩哟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容