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

相关文章
相关标签/搜索