runloop

作者: QYCD | 来源:发表于2021-11-16 15:06 被阅读0次
什么是runloop

顾名思义,runloop就是在"跑圈",本质是一个do-while循环。runloop提供了这么一个机制,当有任务处理时,线程的runloop会保持忙碌,而在没有任何任务处理时,会让线程休眠,从而让出CPU,当再次有任务需要处理时,runloop会被唤醒来处理事件,直到任务处理完毕,再次进入休眠。

  • 保持程序的持续运行
  • 处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
  • 节省CPU资源,提高程序性能: 该做事时做事,该休息时休息
runloop与线程
  • 一条线程对应一个runloop对象,每条线程都有唯一一个与之对应的runloop对象
  • runloop保存在一个全局的dictionary中,线程作为key,runloop作为value(key: 线程,value: runloop)
  • runloop对象在第一次获取runloop时创建,销毁则是在线程结束的时候
  • 主线程的runloop对象系统自动帮我们建好了,而子线程的runloop对象需要我们主动创建和维护
  • runloop在第一次获取时创建,在线程结束时销毁

在viewDidLoad方法内(即在主线程中)添加这么一句

[self performSelector:@selector(testAction) withObject:nil afterDelay:2];

- (void)testAction {
    NSLog(@"==%d", [NSThread isMainThread]);
    NSLog(@"testAction");
}

执行后,2秒后打印:
==1
testAction

而如果在子线程中执行,则会发现2秒后并没有打印;performSelector:afterDelay:方法实质会创建一个定时器添加到runloop中,虽然每个线程都有一个runloop,但是只有主线程的runloop是开启的,而子线程的runloop需要我们自己run起来,定时器才会执行

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
         // 不执行
        [self performSelector:@selector(testAction) withObject:@3 afterDelay:2];
    });

修改为:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self performSelector:@selector(testAction) withObject:@3 afterDelay:2];
        [[NSRunLoop currentRunLoop] run];
    });
执行打印:
==0
testAction

app程序,函数入口main函数。
UIApplicationMain函数内部帮我们开启了主线程的runloop,内部拥有一个无限循环的代码,这样UIApplicationMain函数就不会立刻返回,只要程序不退出/崩溃,就一直循环。

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
runloop五种模式

一个 RunLoop 在某个 mode 下运行时,不会接收和处理其他 mode 的事件

  • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行

  • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响

  • UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode

  • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到

  • kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode

应用场景
  • 可以控制定时器在特定模式下执行--保证NSTimer在有视图滑动时依然能正常执行

创建定时器的两种方式,在主线程,第一种方式会自动将定时器添加到当前runloop并且执行(run)的;第二种需要我们手动添加到当前runloop

// 方式1 默认将定时器添加到NSDefaultRunLoopMode模式
    NSTimer *timer1 = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(run) userInfo:nil repeats:YES];
    [timer1 fire]; //立即执行
    
    // 方式2
    NSTimer *timer2 = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(run) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer2 forMode:NSDefaultRunLoopMode];
    [timer2 fire];

当页面滑动时当前runloop的mode从NSDefaultRunLoopMode切换到UITrackingRunLoopMode,此时原先的NSDefaultRunLoopMode事件将不再处理,不再滑动时再次切换会NSDefaultRunLoopMode后,定时器事件才继续执行;如果创建定时器时定义NSRunLoopCommonModes模式,则即使页面滑动也不会影响定时器事件的执行

  • UIImageView延迟加载图片
    使用performSelector: withObject: afterDelay: inModes:方法将imageView图片的加载放到NSDefaultRunLoopMode,保证滑动视图的流畅性

  • 开启一个常驻线程

iOS RunLoop总结

相关文章

网友评论

      本文标题:runloop

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