深刻淺出 RunLoop(一):初識
深刻淺出 RunLoop(二):數據結構
深刻淺出 RunLoop(三):事件循環機制
深刻淺出 RunLoop(四):RunLoop 與線程
深刻淺出 RunLoop(五):RunLoop 與 NSTimer
深刻淺出 RunLoop(六):相關面試題html
蘋果官方文檔中,RunLoop
的相關介紹寫在線程編程指南中,可見RunLoop
和線程的關係不通常。Threading Programming Guide(蘋果官方文檔)面試
RunLoop
對象和線程是一一對應關係;RunLoop
保存在一個全局的Dictionary
裏,線程做爲key
,RunLoop
做爲value
;RunLoop
建立時機:線程剛建立時並無RunLoop
對象,RunLoop
會在第一次獲取它時建立(獲取方式已經在《深刻淺出 RunLoop(一):初識》文章中講到);RunLoop
銷燬時機:RunLoop
會在線程結束時銷燬;RunLoop
已經自動獲取(建立),子線程默認沒有開啓RunLoop
;RunLoop
對象是在UIApplicationMain
中經過[NSRunLoop currentRunLoop]
獲取,一旦發現它不存在,就會建立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
:併發
// 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
/Observer
,RunLoop
會立馬退出);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