RunLoop的簡單使用

run loop簡介

run loop是一個關聯線程的基礎設施。run loop是一個事件處理循環,主要用於安排工作和接收事件。它可以讓線程在有工作的時

候繁忙,沒有工作的時候休眠。如果沒有run loop的話,你需要創建while或者for循環來不斷監聽事件,但這樣性能低下。

每個線程都有一個run loop,你不必手動創建run loop,可以通過CFRunLoopGetCurrent()或者[NSRunLoop currentRunLoop]獲

取當前線程的run loop。主線程默認開啓run loop並接收用戶事件。當你點擊按鈕時,此時對應的點擊事件會觸發並把事件分配到

run loop並處理事件對應的Handler。

run loop事件分爲兩種,一種是輸入源,一種是定時器源。輸入源又分爲Custom輸入源和Port輸入源。Custom輸入源是客服端發

送信號,Port輸入源是系統發送信號。定時器源是週期性發送信號的源,它發送同步事件。輸入源發送異步事件,通常來自其它線

程或者其它應用。

run loop mode是輸入源和定時器源的集合。你可以創建自己的mode(只需要傳遞一個字符串)或者使用系統提供的mode。只有

run loop運行在特定的mode下,該mode下的源纔會被監視並處理事件。

一下是系統提供的mode。


大多數情況下使用的是Default mode,也可以是Common mode,它包含Default mode、Model mode、Event tracking mode。

主線程是默認開啓run loop,其它線程需要手動開啓run loop。所以在其它線程使用NSTimer,需要把NSTimer添加到線程的run loop。

- (void)myThread2 {
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(fireTimer:) userInfo:nil repeats:YES];
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop addTimer:timer forMode:NSDefaultRunLoopMode];
    [runloop run];
}

run loop會自動創建自動釋放池。每次run loop休眠時會釋放自動釋放池的對象。

cocoa的selector源會自動加入到當前的run loop上,selector執行完畢後會自動從run loop移除。在其它線程中,你需要手動開啓

run loop,否則selector不會執行。

- (void)myThread3 {
    [self performSelector:@selector(myMethod) withObject:nil afterDelay:0];
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop run];
}

run loop的簡單應用

- (void)myThread {
    NSLog(@"線成開始");
    _runloop = CFRunLoopGetCurrent();
    [NSRunLoop currentRunLoop];
    CFRunLoopSourceContext context = {0, (__bridge void *)(self), NULL, NULL, NULL, NULL, NULL, schedule, cancel, perform};
    _source = CFRunLoopSourceCreate(NULL, 0, &context);
    CFRunLoopAddSource(_runloop, _source, kCFRunLoopDefaultMode);
    CFRunLoopObserverContext observerContext = {0, (__bridge void *)(self), NULL, NULL, NULL};
    _observer = CFRunLoopObserverCreate(NULL, kCFRunLoopAllActivities, true, 0, observeCallback, &observerContext);
    CFRunLoopAddObserver(_runloop, _observer, kCFRunLoopDefaultMode);
    BOOL done = NO;
    do {
        CFRunLoopRunResult result =  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
        switch (result) {
            case kCFRunLoopRunFinished:
                NSLog(@"kCFRunLoopRunFinished");
            case kCFRunLoopRunStopped:
                NSLog(@"kCFRunLoopRunStopped");
                done = YES;
                break;
            case kCFRunLoopRunTimedOut:
                NSLog(@"kCFRunLoopRunTimedOut");
                break;
            case kCFRunLoopRunHandledSource:
                NSLog(@"kCFRunLoopRunHandledSource");
                break;
        }
    } while (!done);
    NSLog(@"線程結束");
}

這裏CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false)的10代表run loop運行的時間(單位爲秒)。當明確地停止run 

loop或者沒有源在run loop時,run loop會退出而且線程結束。

CFRunLoopSourceContext的最後一個參數是一個C函數指針,用於處理事件的。info指針是自定義參數,用於傳遞給註冊函數的

info參數。CFRunLoopSourceContext的結構如下:

typedef struct {
    CFIndex	version;
    void *	info;
    const void *(*retain)(const void *info);
    void	(*release)(const void *info);
    CFStringRef	(*copyDescription)(const void *info);
    Boolean	(*equal)(const void *info1, const void *info2);
    CFHashCode	(*hash)(const void *info);
    void	(*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void	(*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void	(*perform)(void *info);
} CFRunLoopSourceContext;

以下是註冊函數的實現

void perform(void *info) {
    ViewController *vc = (__bridge ViewController *)info;
    NSLog(@"執行開始-info=%@", vc);
    sleep(1);
    NSLog(@"執行結束-info=%@", vc);
}

void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
    ViewController *vc = (__bridge ViewController *)info;
    NSLog(@"schedule info=%@,mode=%@", vc, mode);
}

void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
    ViewController *vc = (__bridge ViewController *)info;
    NSLog(@"cancel info=%@,mode=%@", vc, mode);
}

schedule函數會在添加源的時候執行。perform函數會在事件到來時執行。cancel函數會在源失效時執行。

向源發送信號並喚醒run loop,觸發perform函數。

- (IBAction)clickButton:(id)sender {
    if (CFRunLoopIsWaiting(_runloop)) {
        if (CFRunLoopSourceIsValid(_source)) {
            NSLog(@"發送信號");
            CFRunLoopSourceSignal(_source);
            CFRunLoopWakeUp(_runloop);
        } else {
            NSLog(@"CFRunLoopSourceInvalidate");
        }
    } else {
        NSLog(@"CFRunLoopIsWaiting");
    }
}

讓源失效,觸發cancel函數。

- (IBAction)clickCancelBtn:(id)sender {
    if (CFRunLoopIsWaiting(_runloop)) {
        NSLog(@"取消源");
        CFRunLoopSourceInvalidate(_source);
    }
}

手動停止run loop。

- (IBAction)clickStopButton:(id)sender {
    if (CFRunLoopIsWaiting(_runloop)) {
        NSLog(@"停止RunLoop");
        CFRunLoopStop(_runloop);
    }
}

移除源。

- (IBAction)clickRemoveButton:(id)sender {
    if (CFRunLoopIsWaiting(_runloop)) {
        NSLog(@"移除源");
        CFRunLoopRemoveSource(_runloop, _source, kCFRunLoopDefaultMode);
    }
}

添加run loop觀察者。

CFRunLoopObserverContext observerContext = {0, (__bridge void *)(self), NULL, NULL, NULL};
    _observer = CFRunLoopObserverCreate(NULL, kCFRunLoopAllActivities, true, 0, observeCallback, &observerContext);
    CFRunLoopAddObserver(_runloop, _observer, kCFRunLoopDefaultMode);

observeCallBack是C函數指針,用於處理觀察事件。info指針是自定義參數,用於傳遞給註冊函數的info參數。

void observeCallback(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;
    }
}

每次運行run loop,線程的run loop會自動處理之前未處理的信息,並通知相關觀察者。具體的順序如下:

1. 通知觀察者run loop已經啓動。

2. 通知觀察者任何即將要開始的定時器。

3. 通知觀察者任何即將啓動的非基於端口的源。

4. 啓動任何準備好的非基於端口的源。

5. 如果基於端口的源準備好並處於等待狀態,立即啓動;並進入步驟9。

6. 通知觀察者線程進入休眠。

7. 將線程置於休眠知道任一下面事件發生:

    • 某一事件到達基於端口的源。

    • 定時器啓動。

    • run loop設置的時間已經超時。

    • run loop被顯示喚醒。

8. 通知觀察者線程將被喚醒。

9. 處理未處理的事件。

    • 如果用戶定義的定時器啓動,處理定時器事件並重啓run loop。進入步驟2。

    • 如果輸入源啓動,傳遞相應的消息。

    • 如果run loop被顯式喚醒並且時間還沒超時,重啓run loop。進入步驟2。

10. 通知觀察者run loop結束。

當run loop還有源但事件滅有到來時,run loop進入休眠等待狀態。一下是打印的log。

2017-10-25 16:03:20.434434+0800 aa[5027:3503969] 發送信號
2017-10-25 16:03:20.434853+0800 aa[5027:3504031] kCFRunLoopAfterWaiting
2017-10-25 16:03:20.434914+0800 aa[5027:3504031] kCFRunLoopBeforeTimers
2017-10-25 16:03:20.434948+0800 aa[5027:3504031] kCFRunLoopBeforeSources
2017-10-25 16:03:20.435070+0800 aa[5027:3504031] 執行開始-info=<ViewController: 0x102b0eb90>
2017-10-25 16:03:21.436837+0800 aa[5027:3504031] 執行結束-info=<ViewController: 0x102b0eb90>
2017-10-25 16:03:21.436988+0800 aa[5027:3504031] kCFRunLoopBeforeTimers
2017-10-25 16:03:21.437047+0800 aa[5027:3504031] kCFRunLoopBeforeSources
2017-10-25 16:03:21.437096+0800 aa[5027:3504031] kCFRunLoopBeforeWaiting
2017-10-25 16:03:22.467261+0800 aa[5027:3503969] 發送信號
2017-10-25 16:03:22.467683+0800 aa[5027:3504031] kCFRunLoopAfterWaiting
2017-10-25 16:03:22.467715+0800 aa[5027:3504031] kCFRunLoopBeforeTimers
2017-10-25 16:03:22.467733+0800 aa[5027:3504031] kCFRunLoopBeforeSources

run loop的使用環境

1. 需要一個輔助線程來監聽和處理事件,但需要消耗較低的性能。

2. 使用自定義源或者端口源來和其它線程通訊。

3. 在子線程使用定時器。

4. 在子線程使用任何performSelector方法。

5. 保持線程週期性執行任務。

儘量避免手動停止run loop,應該讓run loop在一定時間內退出或者把所有源移除後自動退出。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章