阅读 149

RunLoop的深入分析(深入理解runloop)

Runloop可以保证线程的持续运行,并且让所在线程可以随时响应并处理事件。

主要内容:

  1. Runloop的认识

  2. Runloop对象的认识

  3. Runloop的执行过程

  4. 具体使用

Runloop的源码:下载地址RunLoop的官方文档:官方文档

1、Runloop的认识

1.1 RunLoop是什么

Runloop本质是一个对象,可以实现事件循环机制,也就是让所在线程可以随时响应并处理事件。

Runloop可以看做是闲等待的do-while的循环。它和普通的do-while循环一样处在运行状态,但是在等待的过程中处于运行状态的同时并不会消耗CPU性能。

作用:

  • 保持程序持续运行

    • 程序一启动就会开一个主线程,主线程一开起来就会跑一个主线程对应的RunLoop,RunLoop保证主线程不会被销毁,也就保证了程序的持续运行

  • 节省CPU资源,提高程序性能

    • 线程在没有消息处理时休眠,有消息到来时立刻被唤醒

  • 响应事件

    • 定时器(Timer)、方法调用(PerFormSelecotr)

    • GCD Async Main Queue

    • 事件响应、手势识别、界面刷新

    • 网络请求

    • 自动释放池autoreleasePool

1.2 闲等待的验证

RunLoop可以节省资源,是一种闲等待的do...while查看两种类型下的CPU发现,RunLoop执行循环不会使用CPU资源,而do...while一直在使用CPU资源

正常的do...whiledowhilt循环.gif

Runloop循环RunLoop循环.gif

1.3 主线程Runloop的启动

我们知道Runloop可以保证所在线程的持续运行,而在iOS中程序是可以持续运行的,这说明主线程的Runloop起到了作用,那么它是什么时候开启的呢?

既然主线程一开起来,就需要持续运行,那么猜测应该是在开启主线程的时候开启的Runloop的。因此我们在程序的入口main函数中查找

进入到UIApplicationMain中发现无法看到具体实现

启动后通过在控制台打印可以确实进行了RunLoop的执行

主RunLoop.png

2、Runloop对象的认识

接下来会通过源码查看Runloop在底层到底是个什么东西,

我们在上层使用NSRunloop,属于Foundation框架。 但是在打印堆栈的时候就可以发现NSRunLoop其实是基于CoreFoundation框架的CFRunLoopRef的封装。所以我们往下层需要在CoreFoundation源码中的CFRunLoopRef对象进行分析。

例如:

NSTimer底层通过RunLoop实现的.png

2.1 CFRunLoopRef对象

CFRunLoopRef就是Runloop本身,是NSRunloop在底层的实现

源码:

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* locked for accessing mode list */
    __CFPort _wakeUpPort;            // //通过该函数CFRunLoopWakeUp内核向该端口发送消息可以唤醒runloop
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;//所属线程
    uint32_t _winthread;
    CFMutableSetRef _commonModes;//它的commonModes
    CFMutableSetRef _commonModeItems;//包含的commonModeItems
    CFRunLoopModeRef _currentMode;//当前mode
    CFMutableSetRef _modes;//包含的所有modes
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};复制代码

说明:

  • 可以看出Runloop是有自己所属线程的

  • 同时包含有commonModes和commonModeItems,这个可以称作通用mode,它包含了其他的mode,详细的下面会说

  • 一个Runloop包含多个mode,并且可以获取到当前的mode

  • 因此Runloop和mode是一对多关系,但是同一时间只能使用一个mode

2.2 CFRunLoopModeRef对象

CFRunLoopModeRef对象是Runloop的运行模式

typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  
    CFStringRef _name;//mode名称,运行模式是通过名称来识别的
    Boolean _stopped;//mode是否被终止
    char _padding[3];
  //整个结构体最核心的部分
---------------------------------------------------------------------------------
    CFMutableSetRef _sources0;//事件源0
    CFMutableSetRef _sources1;//事件源1
    CFMutableArrayRef _observers;//观察者
    CFMutableArrayRef _timers;//定时器
---------------------------------------------------------------------------------
    CFMutableDictionaryRef _portToV1SourceMap; //字典  key是mach_port_t,value是CFRunLoopSourceRef
    __CFPortSet _portSet;//保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; 
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};复制代码

说明:

  • mode是通过名称来识别的,不是通过ID之类的

  • mode可以存储四种item,_sources0、_sources1、_observers、_timers

  • 每种item都可以存储多个。

  • _sources0、_sources1是Set结构存储的

  • _observers、_timers是数组结构存储的

RunLoop结构.png

注意:

  • 因此我们可以得出结论一个Runloop会有多个运行模式mode,但是一次只能允许一个mode,而mode可以有多个事件,有四种类型,事件源0,事件源1,监听器,时间源。

  • Runloop管理mode,mode管理具体的事件

  • Runloop启动时只能选择其中一个mode

  • 如果需要切换mode,只能退出当前的loop,选择mode后重新进入loop

  • 如果mode中没有任何的_sources0、_sources1、_observers、_timers,Runloop会立即退出(下面看源码可知)

  • 我们无法自己创建mode,当传入一个新的mode Name时,Runloop内部发现没有对应的mode时,会自动创建对应的CFRunLoopModeRef。

  • 对于一个Runloop来说,其内部的mode只能增加不能删除

2.2.1 __CFRunLoopMode的五种运行模式

1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
2. UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
5. kCFRunLoopCommonModes: 这是一个通用Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode 
复制代码
  • kCFRunLoopCommonModes其实并不是一种真正的mode,只是一种通用的mode。NSRunLoopCommonModes 实际上是一个 Mode 的集合,默认包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode

  • 其中mode在苹果文档中提及的有五个,而在iOS中公开暴露出来的只有 NSDefaultRunLoopMode 和 NSRunLoopCommonModes。

2.2.2 CommonModes的认识

CommonModes需要单独进行分析,它有其特殊的作用

看一下我们将_commonModes添加到Runloop之后会进行什么操作

源码:

void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
    if (!CFSetContainsValue(rl->_commonModes, modeName)) {
        //获取所有的_commonModeItems
        CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
        //获取所有的_commonModes
        CFSetAddValue(rl->_commonModes, modeName);
        if (NULL != set) {
            CFTypeRef context[2] = {rl, modeName};
            // 将所有的_commonModeItems逐一添加到_commonModes里的每一个Mode
            CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
            CFRelease(set);
        }
    }
}复制代码

说明:

  • 可以看到当将commonMode添加到Runloop中时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 Common标记的所有 Mode 里

  • 也就是说当其他mode的事件标记为common时,相当于这个事件同时处于commonMode中。

  • 所以commonMode可以同时含有其他mode中的事件。这也就是公共mode的意义。

2.2.3 ModeItem

ModeItem就是mode包含的事件项,包括_sources0、_sources1、_observers、_timers。

  • Runloop需要处理的消息:_sources0、_sources1、_timers

  • Runloop用来监听状态的对象:_observers

API:

//添加source事件源
CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
//添加监听者
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
//添加时间源
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
//移除事件源
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
//移除监听者
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
//移除时间源
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);复制代码

注意:

  • 一个mode可以包含多个modeItem,一个modeItem也可以同时加入到多个mode中,但是一个item重复加入到同一个mode时只表现为有一个

  • 如果一个mode中没有item,Runloop会直接退出,不进入循环

  • mode对外实现事件就是通过modeItem

2.2.4 mode的简单使用

来看一个NSTimer计时不准确的问题。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    NSLog(@"WY:滑动中%@",[NSRunLoop currentRunLoop].currentMode);
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
    NSLog(@"WY:停止滑动%@",[NSRunLoop currentRunLoop].currentMode);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    NSLog(@"WY:触摸%@",[NSRunLoop currentRunLoop].currentMode);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
    // 加入到RunLoop中才可以运行
    // 1. 把定时器添加到RunLoop中,并且选择默认运行模式NSDefaultRunLoopMode = kCFRunLoopDefaultMode
    // [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    // 当textFiled滑动的时候,timer失效,停止滑动时,timer恢复
    // 原因:当textFiled滑动的时候,RunLoop的Mode会自动切换成UITrackingRunLoopMode模式,因此timer失效,当停止滑动,RunLoop又会切换回NSDefaultRunLoopMode模式,因此timer又会重新启动了
    
    // 2. 当我们将timer添加到UITrackingRunLoopMode模式中,此时只有我们在滑动textField时timer才会运行
    // [[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    
    // 3. 那个如何让timer在两个模式下都可以运行呢?
    // 3.1 在两个模式下都添加timer 是可以的,但是timer添加了两次,并不是同一个timer
    // 3.2 使用站位的运行模式 NSRunLoopCommonModes标记,凡是被打上NSRunLoopCommonModes标记的都可以运行,下面两种模式被打上标签
    //0 : <CFString 0x10b7fe210 [0x10a8c7a40]>{contents = "UITrackingRunLoopMode"}
    //2 : <CFString 0x10a8e85e0 [0x10a8c7a40]>{contents = "kCFRunLoopDefaultMode"}
    // 因此也就是说如果我们使用NSRunLoopCommonModes,timer可以在UITrackingRunLoopMode,kCFRunLoopDefaultMode两种模式下运行
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    NSLog(@"%@",[NSRunLoop mainRunLoop]);
}
-(void)show
{
    NSLog(@"-------");
}复制代码

效果.gif

说明:

  • 可以看到我们使用kCFRunLoopDefaultMode时,当滑动ScrollView,NSTimer就暂停了

  • 当我们使用UITrackingRunLoopMode就只能在滑动ScrollView时运行NSTimer。

  • 而我们使用NSRunLoopCommonModes时,滑动或者不滑动ScrollView,均会运行NSTimer。

  • 这是因为kCFRunLoopDefaultMode和UITrackingRunLoopMode的items默认都在NSRunLoopCommonModes的modeItems中

2.3 CFRunLoopSourceRef对象

CFRunLoopSourceRef是响应事件的对象,当有事件发生时响应

源码:

typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;//执行顺序
    CFMutableBagRef _runLoops;//包含多个RunLoop
    //版本
    union {
        CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;   /* immutable, except invalidation */
    } _context;
};复制代码

说明:

  • __CFRunLoopSource结构体有两种类型CFRunLoopSourceContext和CFRunLoopSourceContext1。

  • union是联合体,二者取其一。

2.3.1 source0

Source0用来实现用户事件

源码:

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;复制代码

说明:

  • Source0 只包含了一个回调(函数指针),它并不能主动触发事件。

  • 使用时,你需要先调用 CFRunLoopSourceSignal (source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp (runloop) 来唤醒 RunLoop,让其处理这个事件。

2.3.2 source1

Source1用来实现系统事件和基于Port的线程间通信,Source1可以主动触发事件,主动唤醒事件

源码:

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);
#if TARGET_OS_OSX || TARGET_OS_IPHONE
    mach_port_t	(*getPort)(void *info);
    void *	(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
    void *	(*getPort)(void *info);//操作port
    void	(*perform)(void *info);//回调函数
#endif
} CFRunLoopSourceContext1;复制代码

说明:

  • Source1包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。

  • 这种Source 能主动唤醒 RunLoop 的线程

总结:

source的认识.jpg

2.4 CFRunLoopTimerRef对象

CFRunLoopTimerRef是基于时间的触发器,属于时间源

源码:

typedef struct __CFRunLoopTimer * CFRunLoopTimerRef;
struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;//所属Runloop
    CFMutableSetRef _rlModes;//包含timer的mode集合
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;       /* immutable */
    CFTimeInterval _tolerance;          /* mutable */
    uint64_t _fireTSR;          /* TSR units */
    CFIndex _order;         /* immutable */
    CFRunLoopTimerCallBack _callout;//timer的回调
    CFRunLoopTimerContext _context;//上下文对象
};复制代码
  • CFRunLoopTimerRef 是基于时间的触发器,也有一个回调函数。

  • 当时间点到达时就可以唤醒Runloop执行这个回调函数

总结:

CFRunLoopTimerRef总结.jpg

2.5 CFRunLoopObserverRef对象

CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。

源码:

typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;//监听的RunLoop
    CFIndex _rlCount;//添加该Observer的RunLoop对象个数
    CFOptionFlags _activities;      /* immutable */
    CFIndex _order;//同时间最多只能监听一个
    CFRunLoopObserverCallBack _callout;//监听的回调
    CFRunLoopObserverContext _context;//上下文用于内存管理
};复制代码

观测的时间点有以下几个

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),   //   即将进入RunLoop
    kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
    kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),// 即将退出RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};复制代码

总结:

CFRunLoopObserverRef总结.jpg

简单使用:

- (IBAction)clickTest:(id)sender {
       // 创建子线程并开启
        NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(show) object:nil];
        thread.name = @"wyThread";
        self.thread = thread;
        [self.thread start];
}

//在子线程中增加一个RunLoop
-(void)show
{
    // 注意:打印方法一定要在RunLoop创建开始运行之前,如果在RunLoop跑起来之后打印,RunLoop先运行起来,已经在跑圈了就出不来了,进入死循环也就无法执行后面的操作了。
    // 但是此时点击Button还是有操作的,因为Button是在RunLoop跑起来之后加入到子线程的,当Button加入到子线程RunLoop就会跑起来
    NSLog(@"%s",__func__);
    // 1.创建子线程相关的RunLoop,在子线程中创建即可,并且RunLoop中要至少有一个Timer 或 一个Source 保证RunLoop不会因为空转而退出,因此在创建的时候直接加入
    
    
    // 添加Source [NSMachPort port] 添加一个端口
    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    
    // 添加一个Timer
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    
    //创建监听者
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"RunLoop进入");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"RunLoop要处理Timers了");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"RunLoop要处理Sources了");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"RunLoop要睡眠了");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"RunLoop被唤醒了");
                break;
            case kCFRunLoopExit:
                NSLog(@"RunLoop退出了");
                break;

            default:
                break;
        }
    });
    // 给RunLoop添加监听者
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);//这里并不是创建RunLoop,只是把当前的RunLoop放到这个线程中
    
    // 2.子线程需要开启RunLoop
    [[NSRunLoop currentRunLoop] run];//run用来开启RunLoop
    CFRelease(observer);
}复制代码

结果:

2021-11-28 17:42:09.194876+0800 RunloopTest[19348:4762406] -[ViewController show]
2021-11-28 17:42:09.195422+0800 RunloopTest[19348:4762406] RunLoop进入
2021-11-28 17:42:09.195535+0800 RunloopTest[19348:4762406] RunLoop要处理Timers了
2021-11-28 17:42:09.195626+0800 RunloopTest[19348:4762406] RunLoop要处理Sources了
2021-11-28 17:42:09.195745+0800 RunloopTest[19348:4762406] RunLoop要睡眠了
2021-11-28 17:42:11.197577+0800 RunloopTest[19348:4762406] RunLoop被唤醒了
2021-11-28 17:42:11.197860+0800 RunloopTest[19348:4762406] <NSThread: 0x600001f9c600>{number = 9, name = wyThread}
2021-11-28 17:42:11.197988+0800 RunloopTest[19348:4762406] RunLoop要处理Timers了
2021-11-28 17:42:11.198094+0800 RunloopTest[19348:4762406] RunLoop要处理Sources了
2021-11-28 17:42:11.198335+0800 RunloopTest[19348:4762406] RunLoop要睡眠了
2021-11-28 17:42:13.200238+0800 RunloopTest[19348:4762406] RunLoop被唤醒了
2021-11-28 17:42:13.200582+0800 RunloopTest[19348:4762406] <NSThread: 0x600001f9c600>{number = 9, name = wyThread}
...复制代码

2.6 RunLoop类的关系

上面逐个认识了每个对象,接下来这些对象的相互关系

Runloop对象的关系.png

说明:

  • 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer。

  • 每次调用 RunLoop的主函数时,只能指定其中一个 Mode,这个Mode被称作CurrentMode。

  • 如果需要切换 Mode,只能退出Loop,再重新指定一个Mode进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

  • 如果一个 mode中一个Source/Timer/Observer 都没有,则RunLoop会直接退出,不进入循环。

3、RunLoop和线程的关系

上文我们知道Runloop是作用于线程的,这里着重看下是如何作用的。

一般在日常开发中,对于RunLoop的获取主要有以下两种方式

// 主运行循环
 CFRunLoopRef mainRunloop = CFRunLoopGetMain();
 // 当前运行循环
 CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();复制代码

接下来通过CFRunLoopGetMain()来探索

3.1 探索RunLoop和线程的关系

3.1.1 进入CFRunLoopGetMain源码

源码:

//得到主RunLoop
CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    //这里的pthread_main_thread_np()就是主线程,通过_CFRunLoopGet0得到主线程的RunLoop
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}复制代码

说明:

  • 返回的是一个RunLoop,通过CFRunLoopRef接收

  • 这里做的是调用_CFRunLoopGet0函数通过主线程获取RunLoop

3.1.2 进入_CFRunLoopGet0

源码:

//通过线程获取RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {//这里也可以看到传入了一个线程pthread_t
    //如果t不存在,则标记为主线程(即默认情况,默认是主线程)
    if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    //如果__CFRunLoops不存在,也就是当前没有任何一个RunLoop
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
        
        //1、创建全局字典CFMutableDictionaryRef,标记为kCFAllocatorSystemDefault
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        
        //2、通过主线程生成一个主RunLoop
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        
    // 3、进行绑定 dict[@"pthread_main_thread_np"] = mainLoop
        //线程指针和RunLoop分别作为key和value存储到一个可变字典中,可以说明是一一对应关系
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        
        
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
        CFRelease(dict);
    }
    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    
    //如果已经有__CFRunLoops了,就通过线程指针在__CFRunLoops获取对应的RunLoop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    //发现如果不存在,也就是该线程还没有RunLoop,再创建RunLoop并存储到字典中
    if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __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是基于线程来管理的,它们一一对应,共同存储在一个全局区的runLoopDict中,线程是key,RunLoop是value。

  • RunLoop的创建:主线程所对应RunLoop在程序一启动创建主线程的时候系统就会自动为我们创建好,子线程所对应的RunLoop并不是在子线程创建出来的时候就创建好的,而是在我们使用该子线程所对应的RunLoop时才创建出来的,换句话说,如果你不获取一个子线程的RunLoop,那么它的RunLoop就永远不会被创建。

  • RunLoop的获取:我们可以通过一个指定的线程从runLoopDict中获取它所对应的RunLoop。

  • RunLoop的销毁:系统在创建RunLoop的时候,会注册一个回调,确保线程在销毁的同时,也销毁掉其对应的RunLoop。

3.2 子线程使用RunLoop

代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // 子线程runloop 默认不启动
    
    self.isStopping = NO;
    LGThread *thread = [[LGThread alloc] initWithBlock:^{
        NSLog(@"%@---%@",[NSThread currentThread],[[NSThread currentThread] name]);
        //通过控制变量isStopping可以停止当前线程,线程停止,RunLoop也就结束,RunLoop结束NSTimer也就停止了
        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"hello word");            // 退出线程--结果runloop也停止了
            if (self.isStopping) {
                [NSThread exit];
            }
        }];
         [[NSRunLoop currentRunLoop] run];
    }];
    thread.name = @"wy.com";
    [thread start];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.isStopping = YES;
}复制代码

运行结果:

2021-11-27 21:21:55.106242+0800 02-Runloop与线程的关系[78338:4082258] <LGThread: 0x600000c44040>{number = 7, name = wy.com}---wy.com
2021-11-27 21:21:56.109209+0800 02-Runloop与线程的关系[78338:4082258] hello word
2021-11-27 21:21:57.109159+0800 02-Runloop与线程的关系[78338:4082258] hello word
2021-11-27 21:21:58.108583+0800 02-Runloop与线程的关系[78338:4082258] hello word
2021-11-27 21:22:15.109605+0800 02-Runloop与线程的关系[78338:4082258] -[LGThread dealloc]---线程销毁了复制代码

说明:

  • 当线程开启后,我们如果不通过[NSRunLoop currentRunLoop]获取RunLoop,子线程是没有RunLoop的

  • 有RunLoop也并不会直接开启,需要通过run方法来启动

  • NSTimer是在子线程中的,而我们知道NSTimer是基于RunLoop实现的,如果不开启子线程的RunLoop就无法启动计时

  • 当启动子线程的RunLoop后,NSTimer会不断执行。不断的打印hello word

  • 我们想要停止执行RunLoop时,就将线程销毁掉,线程销毁RunLoop销毁,NSTimer也就停止执行了。

4、Runloop的执行过程

4.1 事件添加过程

以时间源添加为例源码:

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
    __CFRunLoopLock(rl);
    
    /*
     1、如果添加到kCFRunLoopCommonModes
     */
    
    if (modeName == kCFRunLoopCommonModes) {
        //如果不存在,就先将生成一个_commonModes
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        //将rlt添到RunLoop的_commonModeItems中
        CFSetAddValue(rl->_commonModeItems, rlt);
        
        if (NULL != set) {
            CFTypeRef context[2] = {rl, rlt};
            /* add new item to all common-modes */
            //添加新的item到所有的set集合中
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            CFRelease(set);
        }
    } else {
    /*
     2、添加到非commonMode类型
     */
        //通过名称获取到mode
        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
        
        //如果是第一次设置NSTimer,则创建_timers
        if (NULL != rlm) {
            if (NULL == rlm->_timers) {
                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                cb.equal = NULL;
                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
            }
        }
        
        //判断是否匹配
        if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
            __CFRunLoopTimerLock(rlt);
            if (NULL == rlt->_runLoop) {
        rlt->_runLoop = rl;
          } else if (rl != rlt->_runLoop) {
                __CFRunLoopTimerUnlock(rlt);
            __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
        return;
        }
        //添加mode到Timer中
      CFSetAddValue(rlt->_rlModes, rlm->_name);
        __CFRunLoopTimerUnlock(rlt);
        __CFRunLoopTimerFireTSRLock();
        __CFRepositionTimerInMode(rlm, rlt, false);
        __CFRunLoopTimerFireTSRUnlock();
        if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
            // Normally we don't do this on behalf of clients, but for
            // backwards compatibility due to the change in timer handling...
            if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
        }
        }
            if (NULL != rlm) {
            __CFRunLoopModeUnlock(rlm);
        }
    }
    __CFRunLoopUnlock(rl);
}复制代码

说明:

  • 可以看到有两种,一种添加到kCFRunLoopCommonModes,一种是非kCFRunLoopCommonModes

  • 第一种情况需要添加到RunLoop的_commonModeItems中,这样就称为公共的Item

  • 第二种情况需要创建一个NSTimer对象,并将其中的RunLoop设置为当前RunLoop,再设置mode也设置到NSTimer中

4.2 事件执行过程

上层是通过run方法来执行的,我们先打印来看下

4.2.1 查看堆栈信息

计时器

NSTimer底层通过RunLoop实现的.png

GCD主队列:主线程是基于RunLoop的.png

注:子线程默认没有RunLoop,但可以手动添加,下面会给子线程添加RunLoop来验证

blockblock也是基于RunLoop执行的.png

事件响应RunLoop可以响应事件.png

实现监听Observer通知的监听底层是RunLoop实现的.png

4.2.2 RunLoop的所有响应类型

上面可以看到对于一些事件在底层是RunLoop来响应的。那么RunLoop都有哪些响应呢?

可以在源码中查看一下

//主线程队列
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() __attribute__((noinline));
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(void *msg) {
    _dispatch_main_queue_callback_4CF(msg);
    asm __volatile__(""); // thwart tail-call optimization
}

//Observer监听的响应
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    if (func) {
        func(observer, activity, info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}

//Timer时间源的响应
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info) {
    if (func) {
        func(timer, info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}

//block的响应
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(void (^block)(void)) {
    if (block) {
        block();
    }
    asm __volatile__(""); // thwart tail-call optimization
}

//事件源0的响应
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info) {
    if (perform) {
        perform(info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}

//事件源1的响应
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        void *(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info),
        mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply,
#else
        void (*perform)(void *),
#endif
        void *info) {
    if (perform) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        *reply = perform(msg, size, kCFAllocatorSystemDefault, info);
#else
        perform(info);
#endif
    }
    asm __volatile__(""); // thwart tail-call optimization
}复制代码

总结

  • 我们可以看到所有响应类型有6种

  • 每种响应的过程是一样的,只是最后执行的不一样类型不一样

  • 在底层其实是通过CoreFoundation框架来执行的

  • 执行过程是CFRunLoopRunSpecific->__CFRunLoopRun->__CFRunLoopDoXXX->最后的执行

  • 接下来在底层就按照这个顺序来查看执行的具体过程

  • 上层执行run方法在底层其实是CFRunLoopRun()

RunLoop的所有响应类型.png

4.2.3 查看源码CFRunLoopRun()

上层执行run方法在底层其实是CFRunLoopRun()

源码

//runLoop的启动,可以看到就是do...while循环
void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}复制代码

说明:

  • 这里很明显看到RunLoop的执行其实就是一个do...while循环,只不过是一个闲等待的循环

4.2.4 CFRunLoopRunSpecific()

源码:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    //首先根据modeName找到对应mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
    Boolean did = false;
    if (currentMode) __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopUnlock(rl);
    return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    //再拿到先前的mode
    CFRunLoopModeRef previousMode = rl->_currentMode;
    
    //将获取的新的mode赋给当前的RunLoop
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

    //1、通知 Observers: RunLoop 即将进入 loop。
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    //2、对RunLoop进行run,也就是启动RunLoop,开始跑圈
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    // 通知 Observers: RunLoop 即将退出。
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}复制代码

说明:

  • 这里有三种情况,第一种是启动RunLoop,第二种是对某种事件源进行响应,第三种是结束RunLoop

  • 启动RunLoop和结束RunLoop都是通过Observer来监听实现的

  • RunLoop的mode是通过mode名称来查询的,查询到后再赋值给当前的RunLoop

4.2.5 __CFRunLoopRun()

这个函数代码较多,而且我们只需要看核心的即可,所以只摘出来核心代码

源码:

//核心函数
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode){
    
    //通过GCD开启一个定时器,然后开始跑圈
    dispatch_source_t timeout_timer = NULL;
    ...
    dispatch_resume(timeout_timer);
    
    int32_t retVal = 0;
    
    //处理事务,即处理items
    do {
        
        // 通知 Observers: 即将处理timer事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        // 通知 Observers: 即将处理Source事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
        
        // 处理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 处理sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        
        // 处理sources0返回为YES
        if (sourceHandledThisLoop) {
            // 处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        // 判断有无端口消息(Source1)
        if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
            // 处理消息
            goto handle_msg;
        }
        
        
        // 通知 Observers: 即将进入休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        
        // 等待被唤醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
        
        // user callouts now OK again
        __CFRunLoopUnsetSleeping(rl);
        
        // 通知 Observers: 被唤醒,结束休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
        
    handle_msg:
        if (被timer唤醒) {
            // 处理Timers
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        }else if (被GCD唤醒){
            // 处理gcd
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        }else if (被source1唤醒){
            // 被Source1唤醒,处理Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
        }
        
        // 处理block
        __CFRunLoopDoBlocks(rl, rlm);
        
        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource;//处理源
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;//超时
        } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;//停止
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;//停止
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            retVal = kCFRunLoopRunFinished;//结束
        }
        
        
        
    }while (0 == retVal);
    
    return retVal;
}复制代码

说明:

  • 这里通过do...while循环对所有的事件源进行循环处理,可以看到整个的循环过程

  • do...while循环可以保持持续运行

  • 通过休眠和唤醒可以节省CPU资源,这一轮的消息处理完之后就开始进入休眠状态

  • 一直等到被NSTimer、GCD、source1等事件进行唤醒就继续处理事件

  • 判断如果是Source1,会主动进入到handle_msg唤醒

  • NSTimer的时间点到了之后也会主动唤醒进入到handle_msg唤醒

RunLoop代码流程图.jpg

4.2.6 以Block执行为例

源码:

static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked
...
    CFSetRef commonModes = rl->_commonModes;
    CFStringRef curMode = rlm->_name;
   
    struct _block_item *prev = NULL;
    struct _block_item *item = head;
    while (item) {
        struct _block_item *curr = item;
        item = item->_next;
    Boolean doit = false;
    if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
        //条件一:block所处的mode == 当前的mode
        //条件二:block所处的mode == commonMode,并且block所处的mode添加到了公共mode中
        doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        } else {
        doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
    }
    if (!doit) prev = curr;
    if (doit) {
        if (prev) prev->_next = item;
        if (curr == head) head = item;
        if (curr == tail) tail = prev;
        void (^block)(void) = curr->_block;
            CFRelease(curr->_mode);
            free(curr);
        if (doit) {
            //开始执行block
                __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
            did = true;
        }
            Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc
    }
    }
...
    return did;
}复制代码

说明:

  • rl是当前线程的RunLoop,rlm是当前RunLoop的mode,item是取出runloop中的modeItem

  • 判断执行的条件有两种,第一种:事件源所在的mode是否是当前的mode,如果是就可以执行

  • 第二种,事件源所在的mode是否是CommonModes,并且当前mode也属于CommonModes,如果是就可以执行

  • 如果doit为YES,也就是上面的条件满足,就可以执行__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);。这和我们上面查看的堆栈信息是一致的

4.2.7 总结

  • 黄色:表示通知 Observer 各个阶段;

  • 蓝色:处理消息的逻辑;

  • 绿色:分支判断逻辑;

RunLoop执行过程.jpg

  • 我们将一个事件添加到RunLoop中,有两种情况

    • 第一种:添加到CommonModes中,此时是将事件添加到CommonModesItems,作为公共事件,被标记为common的Item,相当于属于被标记为common的任何mode。

    • 第二种:添加到非CommonModes中。此时先获取到这个mode,将时间源添加到这个mode中的_timers数组中,并且在时间源对象__CFRunLoopTimer中也设置相应的mode和RunLoop。

  • 在执行RunLoop时,所有的事件源都是在do...while循环中不断执行的

  • 当我们run启动RunLoop时,会先启动RunLoop,之后一直处在循环中,处理事件,处理结束后进行休眠,在休眠时被唤醒继续执行,执行完再睡眠,以此循环往复

  • 我们在启动循环、结束循环,事件处理的过程中都会被Observer进行监听

  • 事件的处理有三种,Source0,Source1,Timer

  • Source0不会主动唤醒,Timer和Source1、以及主队列可以唤醒

RunLoop机制.jpg

5、常见使用

5.1 常驻流程

当我们创建一个子线程时,当子线程任务执行完成后线程便销毁了。这是因为子线程默认是没有RunLoop的,想要让子线程可以持续运行,可以给加一个RunLoop并启动,就属于常驻线程了。

5.2 NSTimer的使用

  • NSTimer的使用是基于RunLoop的,而这个RunLoop有多种运行模式mode

  • 不同的mode在不同的情形下有不同的响应事件

  • 在滑动时会从默认mode切换到滑动mode,而主线程中NSTimer是默认添加到默认mode中,所以在滑动mode时并不会响应NSTimer

  • 可以放到CommonModes中,因为系统提供的滑动mode和默认mode都默认属于CommonModes中,而我们将一个事件放到CommonModeItems时,只要标记为Common的mode都会去响应这个事件

CADisplayLink是内核计算的时间,与RunLoop无关,因此会准时

5.3 AutoreleasePool

  • 自动释放池是依赖于线程的RunLoop的,RunLoop启动时创建一个池子,RunLoop休眠时释放掉这个池子

  • RunLoop有Observer进行监听,当监听到将要启动池子时,会进行线程池的开辟,而且优先级是最高的,因为执行其他的事务都有可能使用到池子

  • 当监听到将要销毁池子时,会进行线程池的销毁,而且优先级最低,其他事务执行完成后最后回收垃圾。

5.4 事件响应、手势识别、界面刷新

应用层面的事件响应都是Source1来实现的,当RunLoop处于唤醒状态会执行事件。 系统层面的响应是由Source0,其他线程通过端口向该线程发送msg,之后就唤醒这个线程的Runloop了

5.5. 主线程的使用是通过GCD来唤醒的

在源码中可以看到主线程的执行会唤醒RunLoop,主队列是基于RunLoop启动的

主RunLoop.png

5.6 Block的执行是基于Runloop的

在源码中可以看到Runloop的执行过程中会主动的执行Block

block也是基于RunLoop执行的.png


作者:文乙
链接:https://juejin.cn/post/7035556124543680519

 伪原创工具 SEO网站优化  https://www.237it.com/


文章分类
代码人生
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐