RunLoop

RunLoop

顧名思義

  • 運行循環
  • 在程序運行過程中循環做一些事情
  • 一般來講,一個線程一次只能執行一個任務,執行完成后線程就會退出。如果我們需要一個機制,讓線程能隨時處理事件但并不退出,通常的代碼邏輯是這樣的:
function loop() {
    initialize();
    do {
        var message = get_next_message();
        process_message(message);
    } while (message != quit);
}

這種模型通常被稱作Event Loop
實現這種模型的關鍵點在于:如何管理事件/消息,如何讓線程在沒有處理消息時休眠以避免資源占用、在有消息到來時立即被喚醒。

RunLoop實際上就是一個對象,這個對象管理了其需要處理的事件和消息,并提供了一個入口函數來執行上面的Event Loop的邏輯。線程執行了這個函數后,就會一直處于正常函數內部“接受消息->等待->處理”的循環中,直到這個循環結束(比如傳入quit的消息),函數返回。


應用范疇

  • 定時器(Timer)、performSelector
  • GCD Async Main Queue
  • 事件響應、手勢識別、界面刷新
  • 網絡請求
  • AutoreleasePool

RunLoop的基本作用

  • 保持程序的持續運行
  • 處理App中的各種事件(比如觸摸事件、定時器事件等)
  • 節省CPU資源,提高程序性能:該做事時做事,該休息時休息

RunLoop對象

  • iOS中有2套API來訪問和使用RunLoop
    • Foundation:NSRunLoop
    • Core Foundation:CFRunLoopRef
  • NSRunLoop和CFRunLoopRef都代表著RunLoop對象
    • NSRunLoop是基于CFRunLoopRef的一層OC包裝(所以runloop的地址會不一樣
      • 提供了面向對象的API,但是這些API不是線程安全的
    • CFRunLoopRef是開源的
      • 提供了純C函數的API,所有這些API都是線程安全的
    // OC獲取當前的RunLoop
    NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];
    // OC獲取主線程RunLoop
    NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
    // C語言獲取當前的RunLoop
    CFRunLoopRef currentRunloop2 = CFRunLoopGetCurrent();
    // C語言獲取主線程的RunLoop
    CFRunLoopRef mainRunloop2 = CFRunLoopGetMain();

RunLoop與線程

  • 每條線程都有唯一的一個與之對應的RunLoop對象
  • RunLoop保存在一個全局的Dictionary里,線程作為key,RunLoop作為value
  • 線程剛創建時并沒有RunLoop對象,RunLoop會在第一次獲取它時創建
  • RunLoop會在線程結束時銷毀
  • 主線程的RunLoop已經自動獲取(創建),子線程默認沒有開啟RunLoop
  • 只能在一個線程的內部獲取其RunLoop(主線程除外)。

RunLoop相關的類

  • Core Foundation中關于RunLoop的5個類
    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopSourceRef 事件產生的地方
    • CFRunLoopTimerRef
    • CFRunLoopObserverRef

CFRunLoopRef

typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
    pthread_t _pthread; // RunLoop所對應的線程
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode; // _modes這個集合里面只有一個模式被稱為當前模式_currentMode
    CFMutableSetRef _modes; // 這個集合里面裝了很多CFRunLoopModeRef類型的對象
};  

CFRunLoopModeRef

  • CFRunLoopModeRef代表RunLoop的運行模式
  • 一個RunLoop包含若干個Mode,每個Mode又包含若干個Source0/Source1/Timer/Observer
  • RunLoop啟動時只能選擇其中一個Mode,作為currentMode
  • 如果需要切換Mode,只能退出當前Loop,在重新選擇一個Mode進入
    • 目的:不同組的Source0/Source1/Timer/Observer能分隔開來,互不影響
  • 如果Mode里沒有任何Source0/Source1/Timer/Observer,RunLoop會立馬退出
常見的2種Mode
  • kCFRunLoopDefaultMode(等價于NSDefaultRunLoopMode):App的默認Mode,通常主線程是在這個Mode下運行
  • UITrackingRunLoopMode:界面跟蹤Mode,用于ScrollView追蹤觸摸滑動,保證界面滑動時不受其他Mode影響
  • 另外還有一種模式:kCFRunLoopCommonModes 默認包括kCFRunLoopDefaultMode、UITrackingRunLoopMode兩種模式
typedef struct __CFRunLoopMode * CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFStringRef _name; // mode的名稱
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1; // source里面裝了很多CFRunLoopSourceRef類型的對象
    CFMutableArrayRef _observers;// 里面裝了很多CFRunLoopObserverRef類型的對象
    CFMutableArrayRef _timers; // 里面裝了很多CFRunLoopTimerRef類型的對象
};

CFRunLoopObserverRef

/* RunLoop Observer Activities */
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
};
// 監聽事件的狀態變化(只有c語言接口)
// 方法一
void observeRunLoopActivities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
            break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
            break;
            
        default:
            break;
    }
}

    // 創建observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActivities, NULL);
    // 添加observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 釋放observer
    CFRelease(observer);
    
// 方法二
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopEntry - %@",mode);
                CFRelease(mode);
                break;
            }
            case kCFRunLoopExit:{
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopExit- %@",mode);
                CFRelease(mode);
                break;
            }
                
            default:
                break;
        }
    });
    // 添加observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 釋放observer
    CFRelease(observer);

根據源碼分析,得到如下關系圖

runloop

RunLoop的運行邏輯

  • Source0
    • 觸摸事件處理
    • performSelector: onThread:(提供一個線程,就可以讓對應方法去該線程去執行)
  • Source1
    • 基于Port的線程間通信
    • 系統事件捕捉
  • Timers
    • NSTimer
    • performSelector: withObject: afterDelay:
  • Observers
    • 用于監聽RunLoop的狀態
    • UI刷新(BeforeWaiting)
    • AutoReleasePool(BeforeWaiting)

如下圖所示


runloop流程

RunLoop休眠的實現原理

休眠的實現原理

實質:從用戶態切換到內核態,去等待消息,沒有消息就讓線程休眠,不在占用cpu,有消息就喚醒線程。

RunLoop在實際開發中的應用

  • 控制線程生命周期(線程保活,比如AFNetWorking都是在子線程中等待的,所以是一直保活)

  • 解決NSTimer在滑動時停止工作的問題

static int count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%d",++count);
    }];
// NSDefaultRunLoopMode/UITrackingRunLoopMode才是真正存在的模式    
// NSRunLoopCommonModes并不是一個真的模式,它只是一個標記
// timer能在_commonModes數組中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//    [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
//        NSLog(@"%d",++count);
//    }];
  • 監控應用卡頓
  • 性能優化
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容