• RunLoop


      

    什么是RunLoop?

    从字面意思看,

    运行循环

    跑圈
     
    RunLoop基本作用:
    保持程序的持续运行
    处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)
    节省CPU资源,提高程序性能:该做事时做事,该休息时休息
     
    iOS中有2套API来访问和使用RunLoop
    Foundation
    NSRunLoop
     
    Core Foundation
    CFRunLoopRef

    NSRunLoop和CFRunLoopRef都代表着RunLoop对象

    NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)

    线程与RunLoop:

    每条线程都有唯一的一个与之对应的Runloop对象

    主线程的RunLoop默认是开启的,子线程需要主动开启.

    RunLoop在第一次获取的时候被创建,在线程结束时销毁.

    获取RunLoop对象:

    Foundation框架:

    [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象

    [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

    Core Foundation框架:

    CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象

    CFRunLoopGetMain(); // 获得主线程的RunLoop对象

    RunLoop 相关类:

    Core Foundation中关于RunLoop的5个类
    CFRunLoopRef
    CFRunLoopModeRef
    CFRunLoopSourceRef                           
    CFRunLoopTimerRef
    CFRunLoopObserverRef

    CFRunLoopModeRef:

    CFRunLoopModeRef代表RunLoop的运行模式

    一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer
     
    每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
     
    如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
     
    这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
     
    系统默认注册了5个Mode:
    kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
    UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
    UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
    GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
    kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode
     
     
    CFRunLoopSourceRef是事件源(输入源)
     
    以前的分法
    Port-Based Sources  
    Custom Input Sources
    Cocoa Perform Selector Sources  (self performSelector:<#(nonnull SEL)#> withObject:<#(nullable id)#> afterDelay:<#(NSTimeInterval)#> inModes:<#(nonnull NSArray<NSString *> *)#>)  PerformSelector 就是Cocoa Perform Selector Sources.
     
    现在的分法
    Source0:非基于Port的
    Source1:基于Port的  (Source1接收的事件最终是交给Source0去执行的)
     
    CFRunLoopTimerRef是基于时间的触发器
    基本上说的就是NSTimer
    -(void)test
    {  
        NSTimer * time = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(timer) userInfo:nil repeats:YES];
    
        [[NSRunLoop currentRunLoop]addTimer:time forMode:NSDefaultRunLoopMode];
    
    }
    
    
    -(void)test1
    {
        
        //scheduledTimerWithTimeInterval 默认将NSTimer添加到RunLoop中,并且是默认模式
        NSTimer * time = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timer) userInfo:nil repeats:YES];
        
        //将NSTimer添加到RunLoop中.NSRunLoopCommonModes包含:UITrackingRunLoopMode和kCFRunLoopDefaultMode , 但注意:runloop可以包含多种模式, 但一次只能执行一种模式
        [[NSRunLoop currentRunLoop]addTimer:time forMode:NSRunLoopCommonModes];
        
    }
    
    
    -(void)test2
    {
        CADisplayLink * link = [CADisplayLink displayLinkWithTarget:self selector:@selector(timer)];
        
        link.frameInterval = 60;
        
        //CADisplayLink 需要主动添加到RunLoop中.
        [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
     
    }
    

    注意:当设置了Runloop的模式后, 只能在对应的模式下,定时器才能起作用.

    CFRunLoopObserverRef:

    CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变

    可以监听的时间点有以下几个:
        //创建RunLoop观察者
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopBeforeSources, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            
            NSLog(@"%zd" , activity);
            
        });
        
        //添加监听者
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
        
        //由于是C语言创建出来的监听者, 需要release
        CFRelease(observer);
    

    我么可以通过给RunLoop添加监听者, 去做一些拦截操作,.

    RunLoop处理逻辑-官方版:

    将输入源或定时源添加到RunLoop后, runloop就不的循环处理事件源的一些操作, 若没有事件源输入, runloop就进入休眠状态.

    RunLoop处理逻辑-官方版

    RunLoop处理逻辑-网友整理版

    在第一步之前,可以优化一下:判断Runloop模式中是否有事件源. 有就通知Observer ,即将计入Runloop,否则直接退出.
     
     
    RunLoop应用:
    1.NSTimer:上面已经介绍过了.
     
    2.ImageView显示:(加入有一个需要,在滚动view时,加载图片显示图片, 此时滚动View时,界面就会很卡,此时就可以运用RunLoop,延迟显示图片)
     我们可以这么做:
    我关联了ImageView
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"test.png"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]]; }

    当滚动View时,Runloop处于UITrackingRunLoopMode 模式, 此时performSelector 这个scource 源是Runloop是不会执行的,虽然runloop可以有多种模式,但一次只能在一个模式下运行,执行退出当前模式,才能执行其他模式下的Scource.

    3.常驻线程:(就是让一个线程不死, 一直等待响应某些事情, 获取希望能让一条线程,在后台去监测扫描用户的沙盒里的文件,这个时候需要一直使用线程等等).
    注意:一定不能设置一个Strong 的成员属性来引用线程, 这是不允许的,虽然能保证线程不被dealloc,但当线程被创建后,执行完任务后线程已经进入消亡状态, 是无法再次开启, 更不能执行其他任务.(通过一个死循环,去操作,让这个线程不死)
     
    方法1:(不推荐,消耗性能)
    @interface ViewController () @property(nonatomic ,strong)NSThread * thread; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; YWThread * thread = [[YWThread alloc]initWithTarget:self selector:@selector(run) object:nil]; self.thread = thread; [thread start]; } -(void)run { while (1) { //如果没有这个死循环, Runloop创建后就关闭. 这个死循环让当前线程的Runloop不断的开启关闭.(这样也可以让一个线程不死, 一直接收处理Source) [[NSRunLoop currentRunLoop]run]; } NSLog(@"-------------"); } -(void)test1 { NSLog(@"-----test1-----"); } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self performSelector:@selector(test1) onThread:self.thread withObject:nil waitUntilDone:NO]; }
     
    方法2: 直接给Runloop添加事件源
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        
        YWThread * thread = [[YWThread alloc]initWithTarget:self selector:@selector(run) object:nil];
        
        [thread start];
    }
        
    
    -(void)run
    {
        这样就可以让一个线程不死(runLoop一直循环,线程就不会被dealloc)
        [[NSRunLoop currentRunLoop]addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop]run];
        
        NSLog(@"---run-----");
        
    }
    
     
  • 相关阅读:
    list中的对象或者map中的版本号排序 version排序
    spring boot jpa 复杂查询 动态查询 连接and和or 模糊查询 分页查询
    java 8 list的stream操作 list中的对象中的某一个成员取出转为该成员的list,以及对象过滤,筛选某个属性后的成员
    map或者对象转换
    Feign代理必须加value否则启动失败
    Eclipse中.setting目录下文件介绍
    远程仓库版本回退方法
    SpringMVC异常处理机制
    android studio启动和项目编译问题
    CentOS6.5安装php7+nginx+mysql实现安装WordPress
  • 原文地址:https://www.cnblogs.com/yuwei0911/p/5271081.html
Copyright © 2020-2023  润新知