iOS底层原理系列06-RunLoop原理与应用实践

RunLoop是iOS开发中的核心机制,它使线程能够在没有工作时进入休眠状态,并在有新任务时被唤醒。本文将深入探讨RunLoop的工作原理及其在实际开发中的应用。

1. RunLoop基础结构

1.1 RunLoop对象模型

RunLoop是一个对象,用于处理来自不同源的输入事件,并调度这些事件到对应的处理程序。在iOS中,有两套API可以访问RunLoop:

  • Foundation框架中的NSRunLoop
  • Core Foundation框架中的CFRunLoopRef类型

这两者是紧密关联的,NSRunLoop实际上是对CFRunLoopRef的封装,提供了面向对象的接口。

在这里插入图片描述

每个线程都有且只有一个RunLoop对象与之关联,主线程的RunLoop在应用启动时自动创建并运行,而子线程的RunLoop需要显式创建和运行。

// 获取当前线程的RunLoop
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];

// 获取主线程的RunLoop
NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];

// Core Foundation中的等效调用
CFRunLoopRef currentCFRunLoop = CFRunLoopGetCurrent();
CFRunLoopRef mainCFRunLoop = CFRunLoopGetMain();

1.2 事件源分类与处理

RunLoop处理的事件源分为三种类型:

  1. Source0:非基于端口的输入源,需要手动触发。主要包括:
    • 用户触摸事件
    • 界面刷新事件
    • 自定义事件
  2. Source1:基于端口的输入源,可以自动触发。包括:
    • 系统事件
    • 进程间通信
    • Mach端口事件
  3. Timer:基于时间的触发源,包括:
    • NSTimer
    • CADisplayLink
    • performSelector:withObject:afterDelay:

在这里插入图片描述

1.3 运行模式与切换机制

RunLoop可以在不同的模式下运行,每种模式定义了一组事件源和定时器的集合。RunLoop在某一时刻只能运行在一种模式下,如果想要切换模式,只能退出当前RunLoop,再以新模式重新启动。

常见的RunLoop模式包括:

  • kCFRunLoopDefaultMode (NSDefaultRunLoopMode):默认模式,大多数情况下使用。
  • UITrackingRunLoopMode:用于跟踪UI事件(如滚动)时的模式。
  • kCFRunLoopCommonModes (NSRunLoopCommonModes):一个模式集合,包含Default和Tracking模式。
// 在默认模式下添加定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

// 在UI跟踪模式下添加
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

// 在Common模式下添加(同时在Default和Tracking模式下有效)
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

当界面滚动时,RunLoop会从NSDefaultRunLoopMode切换到UITrackingRunLoopMode,如果Timer只添加到了NSDefaultRunLoopMode,则在滚动时Timer不会被触发。这就是为什么我们经常使用NSRunLoopCommonModes来确保Timer在滚动时也能正常工作。

1.4 底层数据结构剖析

RunLoop的核心是其内部数据结构的设计。以下是Core Foundation中的RunLoop相关数据结构的简化版本:

struct __CFRunLoop {
   
   
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    __CFPort _wakeUpPort;
    Boolean _stopped;
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
};

struct __CFRunLoopMode {
   
   
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFStringRef _name;
    Boolean _stopped;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
};

struct __CFRunLoopSource {
   
   
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;
    CFMutableBagRef _runLoops;
    union {
   
   
        struct {
   
   
            void (*perform)(void *info);
            void *info;
        } version0;
        struct {
   
   
            void *(*retain)(void *info);
            void (*release)(void *info);
            void (*perform)(void *info);
            void *info;
        } version1;
    } _context;
};

struct __CFRunLoopTimer {
   
   
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;
    CFTimeInterval _tolerance;
    uint64_t _fireTSR;
    CFIndex _order;
    void (*_callout)(CFRunLoopTimerRef timer, void *info);
    void *_context;
};

在这里插入图片描述

2. RunLoop与线程

2.1 主线程RunLoop特性

主线程的RunLoop具有一些特殊的特性:

  1. 自动创建和启动:主线程的RunLoop在应用启动时自动创建并启动,不需要手动干预。
  2. 处理UI事件:主线程RunLoop负责处理所有的UI事件,包括触摸、手势等。
  3. 与应用生命周期绑定:主线程RunLoop会一直运行,直到应用终止。
  4. 集成系统框架:主线程RunLoop集成了UIKit、CoreAnimation等框架的事件处理。
// 主线程RunLoop已自动启动,不需要手动启动
NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];

// 可以添加额外的输入源或定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(onTimer:) userInfo:nil repeats:YES];
[mainRunLoop addTimer:timer forMode:NSRunLoopCommonModes];

2.2 RunLoop线程管理机制

RunLoop与线程之间存在着密切的关系:

  1. 一对一关系:每个线程都有且只有一个RunLoop对象,且不能共享。
  2. 懒创建:除主线程外,其他线程的RunLoop需要显式获取才会被创建。
  3. 自动销毁:RunLoop会随着线程的结束而销毁。
  4. 线程保活:通过启动RunLoop可以保持线程不退出,实现线程常驻。
// 创建一个常驻线程
+ (void)createPermanentThread {
   
   
    static NSThread *permanentThread = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
   
   
        permanentThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadEntryPoint) object:nil];
        [permanentThread setName:@"PermanentThread"];
        [permanentThread start];
    });
}

// 线程入口函数
+ (void)threadEntryPoint {
   
   
    @autoreleasepool {
   
   
        NSRunLoop 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值