深刻淺出 RunLoop(四):RunLoop 與線程

RunLoop 系列文章

深刻淺出 RunLoop(一):初識
深刻淺出 RunLoop(二):數據結構
深刻淺出 RunLoop(三):事件循環機制
深刻淺出 RunLoop(四):RunLoop 與線程
深刻淺出 RunLoop(五):RunLoop 與 NSTimer
深刻淺出 RunLoop(六):相關面試題html

RunLoop 與線程的關係

蘋果官方文檔中,RunLoop的相關介紹寫在線程編程指南中,可見RunLoop和線程的關係不通常。Threading Programming Guide(蘋果官方文檔)面試

  • RunLoop對象和線程是一一對應關係;
  • RunLoop保存在一個全局的Dictionary裏,線程做爲keyRunLoop做爲value
  • RunLoop建立時機:線程剛建立時並無RunLoop對象,RunLoop會在第一次獲取它時建立(獲取方式已經在《深刻淺出 RunLoop(一):初識》文章中講到);
  • RunLoop銷燬時機:RunLoop會在線程結束時銷燬;
  • 主線程的RunLoop已經自動獲取(建立),子線程默認沒有開啓RunLoop
  • 主線程的RunLoop對象是在UIApplicationMain中經過[NSRunLoop currentRunLoop]獲取,一旦發現它不存在,就會建立RunLoop對象。

開啓子線程的 RunLoop 的過程

獲取 RunLoop 對象

能夠經過如下方式來獲取RunLoop對象:編程

// Foundation
    [NSRunLoop mainRunLoop];     // 獲取主線程的 RunLoop 對象
    [NSRunLoop currentRunLoop];  // 獲取當前線程的 RunLoop 對象
    // Core Foundation
    CFRunLoopGetMain();     // 獲取主線程的 RunLoop 對象
    CFRunLoopGetCurrent();  // 獲取當前線程的 RunLoop 對象
複製代碼

咱們來看一下CFRunLoopGetCurrent()函數是怎麼獲取RunLoop對象的:數據結構

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());  // 調用 _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) {
    if (pthread_equal(t, kNilPthreadT)) {
	t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
	CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
	CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
	CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
	if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
	    CFRelease(dict);
	}
	CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    // ️當前線程做爲 Key,從 __CFRunLoops 字典中獲取 RunLoop 
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if (!loop) {  // ️若是字典中不存在
	    CFRunLoopRef newLoop = __CFRunLoopCreate(t);  // 建立當前線程的 RunLoop
        __CFLock(&loopsLock);
	    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    	if (!loop) {
	        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);  // 保存到字典中
	        loop = newLoop;
    	}
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&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併發

// Foundation
    [[NSRunLoop currentRunLoop] run];
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    // Core Foundation
    CFRunLoopRun();
    CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);  // 第3個參數:設置爲 true,表明執行完 Source/Port 後就會退出當前 loop
複製代碼

咱們來看一下CFRunLoopRun()/CFRunLoopRunInMode()函數是怎麼啓動RunLoop的:app

void CFRunLoopRun(void) {
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {   
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
複製代碼

能夠看到它經過調用CFRunLoopRunSpecific()函數來啓動RunLoop,而該函數實現已在《深刻淺出 RunLoop(三):事件循環機制》文章中講解到。ide

實現一個常駐線程

  • 好處:常常用到子線程的時候,不用一直建立銷燬,提升性能;
  • 條件:該任務需是串行的,而非併發;
  • 步驟:
    ① 獲取/建立當前線程的RunLoop
    ② 向該RunLoop中添加一個Source/Port等來維持RunLoop的事件循環(若是 Mode 裏沒有任何Source0/Source1/Timer/ObserverRunLoop會立馬退出);
    ③ 啓動該RunLoop
  • 示例代碼及測試輸出以下:
// ViewController.m
#import "ViewController.h"
#import "HTThread.h"

@interface ViewController ()
@property (nonatomic, strong) HTThread *thread;
@property (nonatomic, assign, getter=isStoped) BOOL stopped;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    
    self.stopped = NO;
    self.thread = [[HTThread alloc] initWithBlock:^{
        NSLog(@"begin-----%@", [NSThread currentThread]);
        
        // ① 獲取/建立當前線程的 RunLoop 
        // ② 向該 RunLoop 中添加一個 Source/Port 等來維持 RunLoop 的事件循環
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    
        while (weakSelf && !weakSelf.isStoped) {
            // ③ 啓動該 RunLoop
            /* [[NSRunLoop currentRunLoop] run] 若是調用 RunLoop 的 run 方法,則會開啓一個永不銷燬的線程 由於 run 方法會經過反覆調用 runMode:beforeDate: 方法,以運行在 NSDefaultRunLoopMode 模式下 換句話說,該方法有效地開啓了一個無限的循環,處理來自 RunLoop 的輸入源 Sources 和 Timers 的數據 */ 
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        NSLog(@"end-----%@", [NSThread currentThread]);    
    }];
    [self.thread start];
}

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

// 子線程須要執行的任務
- (void)test
{
    NSLog(@"%s-----%@", __func__, [NSThread currentThread]);
}

// 中止子線程的 RunLoop
- (void)stopThread
{
    // 設置標記爲 YES
    self.stopped = YES;   
    // 中止 RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());    
    NSLog(@"%s-----%@", __func__, [NSThread currentThread]);
    // 清空線程
    self.thread = nil;
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    
    if (!self.thread) return;
    // 在子線程調用(waitUntilDone設置爲YES,表明子線程的代碼執行完畢後,當前方法纔會繼續往下執行)
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}

@end


// HTThread.h
#import <Foundation/Foundation.h>
@interface HTThread : NSThread
@end

// HTThread.m
#import "HTThread.h"
@implementation HTThread
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end
複製代碼

點擊 view,接着退出當前 ViewController。輸出以下:函數

begin-----<HTThread: 0x600002b71240>{number = 6, name = (null)}
-[ViewController test]-----<HTThread: 0x600002b71240>{number = 6, name = (null)}
-[ViewController dealloc]
-[ViewController stopThread]-----<HTThread: 0x600002b71240>{number = 6, name = (null)}
end-----<HTThread: 0x600002b71240>{number = 6, name = (null)}
-[HTThread dealloc]oop

相關連接

Threading Programming Guide(蘋果官方文檔)post

相關文章
相關標籤/搜索