說到NSRunLoop我們要說點(diǎn)什么

目前正是找工作、換工作的黃金時(shí)間,對于iOS開發(fā)從業(yè)者來說 面試?yán)@不過去的就是runLoop,那么我們說到runLoop我們應(yīng)該說些什么呢
答題從以下幾方面入手
1、runLoop是什么
2、runLoop的作用是什么
3、runLoop和線程的關(guān)系
4、runLoop之Modes
5、modes之items(source0,source1,timer、observer)
6、runLoop的應(yīng)用

1、runLoop是什么

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* 獲取modeList的鎖 */
    __CFPort _wakeUpPort;            // 喚醒runloop
    Boolean _unused;
    volatile _per_run_data *_perRunData;
    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;
};

首先runloop是一個(gè)運(yùn)行循環(huán),它實(shí)際上也是一個(gè)對象、這個(gè)對象提供了一個(gè)入口函數(shù),使程序進(jìn)入一個(gè)do..while的循環(huán)中,循環(huán)處理各種任務(wù)

2、runLoop的作用是什么

1)由于是運(yùn)行循環(huán),他保持程序持續(xù)的運(yùn)行,即便沒有待處理的任務(wù) 也不退出程序(可利用這點(diǎn)防止程序崩潰)
2)處理App中的各種事件 包括觸摸、滑動(dòng)、performSelector等
3)節(jié)省cpu資源,提高程序的性能,使cpu該做事做事,該休息休息

3、runLoop和線程的關(guān)系

1)線程和runLoop是一一對應(yīng)的關(guān)系,
2)主線程runloop程序啟動(dòng)后自動(dòng)創(chuàng)建,子線程默認(rèn)不創(chuàng)建runloop,直到在子線程中第一次獲取runloop時(shí),才創(chuàng)建runloop,同時(shí)runloop存放在一個(gè)可變字典中 字典的key-value 分別為pthread-runloop

4、runLoop之Modes

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; // timer開啟后為true
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
}

每個(gè)runloop包含多個(gè)mode,但是 同時(shí)一個(gè)runloop只能在一中mode下運(yùn)行,runloop在運(yùn)行時(shí),是在不斷切換mode的,mode的分類

kCFRunLoopDefaultMode:APP的默認(rèn)mode 通常主線程是在這個(gè)model下運(yùn)行
UITrackingRunLoopMode: 界面跟蹤mode,用于scrollview追蹤觸摸滑動(dòng),保證頁面滑動(dòng)時(shí),不受其他mode的影響
UIInitializationRunLoopMode:當(dāng)app剛啟動(dòng)時(shí),進(jìn)入的第一個(gè)mode,啟動(dòng)完成后就不再使用,會切換到kCFRunLoopDefaultMode
GSEventReceiveRunLoopMode:接收系統(tǒng)事件的內(nèi)部mode,通常用不到
kCFRunLoopCommonModes: 這是一個(gè)占位mode,作為標(biāo)記kCFRunLoopDefaultMode和UITrackingRunLoopMode,用并不是一種真正的model,也可以認(rèn)為是一種混合模式

5、modes之items(source0,source1,timer、observer)

一個(gè)mode持有的source、timer、observer,都是集合類型所以說每個(gè)model,可以持有多個(gè)source、timer、observer

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;            /* 不可變 */
    CFMutableBagRef _runLoops;
    union {
    CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;    /* immutable, except invalidation */
    } _context;
};
struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;        /* 不可變 */
    CFTimeInterval _tolerance;          /* 可變 */
    uint64_t _fireTSR;            
    CFIndex _order;            
    CFRunLoopTimerCallBack _callout;    
    CFRunLoopTimerContext _context;    
};

運(yùn)行的時(shí)候 會將相應(yīng)的item添加到對應(yīng)的mode中,即調(diào)用CFSetAddValue方法,之后再在運(yùn)行循環(huán)時(shí)會調(diào)用CFRunLoopDoBlocks方法,判斷當(dāng)前model后,執(zhí)行相應(yīng)的block回調(diào)

CFRunLoopSourceRef: 包括source0 和 source1
source0,處理app內(nèi)部事件,app自己負(fù)責(zé)管理,uiEvent,CFSocket,僅包含一個(gè)函數(shù)指針
底層調(diào)用 創(chuàng)建source0源,將source0加入當(dāng)runloop相應(yīng)的mode中、執(zhí)行signal信號,標(biāo)記待執(zhí)行,執(zhí)行CFRunLoopWakeUp喚醒runloop,處理相應(yīng)的事件,取消移除源CFRunLoopRemoveSource
source1,包含一個(gè)mach_port和一個(gè)回調(diào)指針,一般用于通過內(nèi)核和其他線程進(jìn)行通訊
CFRunLoopTimerRef:timer的底層是一個(gè)CFRunLoopTimerRef,這個(gè)timer是受runloop的mode模式影響的,
創(chuàng)建CFRunLoopTimerRef 添加到相應(yīng)的當(dāng)前runloop,若是子線程的runloop 需要調(diào)用CFRunLoopRun自行開啟,否則 timer是不會調(diào)用的
CFRunLoopObserverRef:觀察者,觀察runloop所處的狀態(tài)
初始化CFRunLoopObserverContext,
創(chuàng)建 CFRunLoopObserverRef,配置回調(diào)方法
添加觀察者當(dāng)當(dāng)前的runloop

6、runLoop的應(yīng)用

1)線程?;?/p>

[[NSRunLoop currentRunLoop] addPort:[NSort port] forMode:NSDefaultRunLoopMode];
while (self.keepAlive) {
    [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 
}

退出線程時(shí)

self.keepAlive = NO;
CFRunLoopStop(CFRunLoopGetCurrent());

2)主線程卡頓檢測
在主線程穿件觀察者,在子線程中觀察observer的狀態(tài),
主線程的操作是 在 kCFRunLoopBeforeSource 和 kCFRunLoopBeforeWaiting分別記錄時(shí)間,判斷時(shí)間差 超過某一個(gè)閾值即認(rèn)為發(fā)生了卡頓,此時(shí)可以獲取堆棧信息,并記錄上傳,以供分析

@interface Minitor ()

@property(nonatomic, strong) NSThread *monitorThread;

@property(nonatomic, strong) NSDate *startDate;

/// 是否正在執(zhí)行任務(wù)
@property(nonatomic, assign, getter=isExcuting) BOOL excuting;

@end

@implementation Minitor {
    
    CFRunLoopObserverRef _observer;
    CFRunLoopTimerRef _timer;
    
}

+ (instancetype)shareInstatance {
    
    static Minitor *instance;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[Minitor alloc] init];
        instance.monitorThread = [[NSThread alloc] initWithTarget:self selector:@selector(moniterThreadEntryPoint) object:nil];
        [instance.monitorThread start];
    });
    
    return  instance;
}

+ (void)moniterThreadEntryPoint {
    
    @autoreleasepool {
        [[NSThread currentThread] setName:@"Monitor"];
        NSRunLoop *runloop = [NSRunLoop currentRunLoop];
        [runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runloop run];
    }
    
}

- (void)statrMonitor {
    
    if (_observer) {
        NSLog(@"已經(jīng)創(chuàng)建了監(jiān)聽");
        return;
    }
    CFRunLoopObserverContext context = {0, (__bridge void *)self, NULL, NULL, NULL};
    _observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
    
    [self performSelector:@selector(addTimerToMonitorThread) onThread:self.monitorThread withObject:nil waitUntilDone:NO modes:@[NSRunLoopCommonModes]];
    
}


static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    
    Minitor *monitor = (__bridge  Minitor*)info;
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
            monitor.startDate = [NSDate date];
            monitor.excuting = YES;
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
            monitor.excuting = NO;
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
            break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
            break;
            
        default:
            break;
    }
    
}



#pragma mark 定時(shí)器

- (void)addTimerToMonitorThread {
    if (_timer) {
        return;
    }
    CFRunLoopRef curentRunLoop = CFRunLoopGetCurrent();
    CFRunLoopTimerContext context = {0, (__bridge void *)self, NULL, NULL, NULL};
    _timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.01, 0.01, 0, 0, &runloopTimerCallBack, &context);
    
    CFRunLoopAddTimer(curentRunLoop, _timer, kCFRunLoopCommonModes);
    
}

static void runloopTimerCallBack(CFRunLoopTimerRef timer, void *info) {
    
    Minitor *monitor = (__bridge Minitor*)info;
    if (!monitor.isExcuting) {
//        kCFRunLoopBeforeWaiting
        return;
    }
    NSTimeInterval excuteTime = [[NSDate date] timeIntervalSinceDate:monitor.startDate];
    
    NSLog(@"定時(shí)器:當(dāng)前線程%@,主線程執(zhí)行時(shí)間:%f秒", [NSThread currentThread], excuteTime);
    
    if (excuteTime >= 0.00001) {
        NSLog(@"卡頓了 %f  秒", excuteTime);
        [monitor handleStackInfo];
    }
    
}

- (void)handleStackInfo {
    
    NSArray *callStackSymbls = [NSThread callStackSymbols];
    [callStackSymbls enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"%@", obj);
    }];
    
}

@end

3)給程序賦予一次回光返照的機(jī)會
參開鏈接傳送門
4)大型列表 加載圖片時(shí),即圖片下載或是顯示 根據(jù)runloop當(dāng)前為 空閑狀態(tài),才開始下載或是顯示圖片,以免顯示圖片造成頁面卡頓

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. 簡介 ??官方的介紹“The programmatic interface to objects that ...
    Timir閱讀 1,255評論 0 1
  • 如果沒有RunLoop main函數(shù)中的RunLoop 第14行代碼的UIApplicationMain函數(shù)內(nèi)部就...
    JonesCxy閱讀 665評論 0 4
  • Runloop 是和線程緊密相關(guān)的一個(gè)基礎(chǔ)組件,是很多線程有關(guān)功能的幕后功臣。盡管在平常使用中幾乎不太會直接用到,...
    jackyshan閱讀 10,009評論 10 75
  • 通常我們開發(fā)iOS app時(shí)接觸到的是NSRunLoop,而NSRunLoop實(shí)際上是對蘋果的Core Found...
    wilsonhan閱讀 367評論 0 0
  • 一、基礎(chǔ)篇 1.RunLoop是什么 RunLoop字面意思是跑圈,實(shí)際就是運(yùn)行循環(huán)(即死循環(huán))其實(shí)它內(nèi)部就是do...
    irenb閱讀 508評論 0 1

友情鏈接更多精彩內(nèi)容