RunLoop

作者: buding_ | 来源:发表于2024-06-15 15:53 被阅读0次
    什么是RunLoop?
    • 顾名思义:运行循环,在程序运行过程中循环做一些事情;
    • 每条线程都有唯一的与之对应的RunLoop对象;
    • RunLoop保存在一个全局的Dictionary中,线程作为key,RunLoop作为value
      线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建;
    • RunLoop会在线程结束时销毁;
    • 主线程的RunLoop已经自动获取,子线程默认没有开启RunLoop;
    RunLoop的基本作用:
    • 保持程序的持续运行
    • 处理App中各种事件
    • 节省CPU资源,提高程序性能(该做事时做事,该休息时休息)
    iOS中有2套API来访问和使用RunLoop
    Foundtation: NSRunLoop
    Core Foundation: CFRunLoopRef
    NSRunLoop和CFRunLoopRef都代表着RunLoop对象
    NSRunLoop是基于CFRunLoopRef的一层OC封装
    
    NSRunLoop *r1 = [NSRunLoop currentRunLoop];
    CFRunLoopRef r2 = CFRunLoopGetCurrent();
    
    
    struct __CFRunLoop {
        pthread_t _pthread;
        CFMutableSetRef _commonModes;
        CFMutableSetRef _commonModeItems;
        CFRunLoopModeRef _currentMode; 
        CFMutableSetRef _modes;
    };
    
    typedef struct __CFRunLoopMode *CFRunLoopModeRef;
    
    struct __CFRunLoopMode {
        CFStringRef _name;
        CFMutableSetRef _sources0;
        CFMutableSetRef _sources1;
        CFMutableArrayRef _observers;
        CFMutableArrayRef _timers;
    };
    Source0: 触摸事件、performSelector:onThread
    Source1: 基于Port的线程间通信、系统事件捕捉
    Timers:NSTimer、performSelector:withObject:afterDelay: 
    Observers: 用于监听RunLoop的状态、UI刷新(BeforeWaiting)、Autorelease pool(BeforeWaiting)
    
    
    CFRunLoopMode
    • CFRunLoopMode代表RunLoop的运行模式
    • RunLopp包含若干个mode,每个mode又包含若干个Source0/ Source1/ Timer/ Observer
    • RunLoop启动时只能选择其中一个mode,作为currentMode
    • 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
    • 不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
    • 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
    常见的2种mode
    kCFRunLoopDefaultMode: App的默认mode,通常主线程就是在这个Mode下运行
    UITrackingRunLoopMode: 界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他mode影响
    
    注意:NSRunLoopCommonModes并不是真的模式,它只是一个标记,设置为后,source会在_commonModes中存放的mode中运行;
    _commonModes中默认存放的是kCFRunLoopDefaultMode和UITrackingRunLoopMode;
    当source设置在NSRunLoopCommonModes模式后,那么该source会被存放到_commonModeItems中
    
    
    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        kCFRunLoopEntry = (1UL << 0),           //即将进入Loop
        kCFRunLoopBeforeTimers = (1UL << 1),    //即将处理Timer
        kCFRunLoopBeforeSources = (1UL << 2),   //即将处理Source
        kCFRunLoopBeforeWaiting = (1UL << 5),   //即将进入休眠
        kCFRunLoopAfterWaiting = (1UL << 6),    //刚才休眠中唤醒
        kCFRunLoopExit = (1UL << 7),            //即将退出Loop
        kCFRunLoopAllActivities = 0x0FFFFFFFU
    };
    
    RunLoop休眠的原理:
    通过mach_msg 从用户态进入内核态,从而可以使线程进入睡眠;   
    
    RunLoop如何响应用户操作?

    CFRunLoop的currentMode中的Source1捕捉到用户事件,包装成事件队列后传递给source0处理;
    通知observers结束休眠,处理source;


    RunLoop运行逻辑.png
    应用范畴:

    定时器、PerformSelector
    GCD Async Main Queue
    事件响应、手势识别、界面刷新

    RunLoop在项目中的应用有

    控制线程的生命周期

    @interface ViewController ()
    @property (nonatomic, strong) NSThread *thread;
    @property (nonatomic, assign) BOOL isStopThread;
    @end
    
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        __weak typeof(self) weakSelf = self;
        self.thread = [[NSThread alloc] initWithBlock:^{
            /*添加NSPort,让RunLoop立马销毁,
              因为当RunLoop中没有source0 source1 observer timer时,会立马销毁 */
            [[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
            //通过isStopThread标记位控制Loop
            while (weakSelf && !weakSelf.isStopThread) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
            //NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的NSRunLoop
            //[[NSRunLoop currentRunLoop] run];
        }];
        [self.thread start];
    }
    - (void)test {
        NSLog(@" __ %s __ %@", __func__, [NSThread currentThread]);
    }
    - (void)stop {
        NSLog(@" __ %s __ %@", __func__, [NSThread currentThread]);
        //修改结束Loop的标记位
        _isStopThread = YES;
        //停止本次Loop
        CFRunLoopStop(CFRunLoopGetCurrent());
    }
    - (IBAction)onTestOnThread:(id)sender {
        [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
    }
    
    - (IBAction)onStopThread:(id)sender {
        [self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES];
    }
    @end
    

    解决NSTimer在滑动时停止工作的问题

    static int count = 0;
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
      NSLog(@"%d",count++);
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    
    注意:NSRunLoopCommonModes并不是真的模式,它只是一个标记,设置为后,source会在_commonModes中存放的mode中运行;
    _commonModes中默认存放的是kCFRunLoopDefaultMode和UITrackingRunLoopMode;
    当source设置在NSRunLoopCommonModes模式后,那么该source会被存放到_commonModeItems中
    
    

    相关文章

      网友评论

          本文标题:RunLoop

          本文链接:https://www.haomeiwen.com/subject/suxnqjtx.html