深刻浅出 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