iOS与NSRunLoop

作者: 搬砖的小红帽 | 来源:发表于2017-08-02 22:50 被阅读0次

    runloop:

    我们的程序为什么运行起来后,不手动终止运行的话,App会一直持续运行?NSRunLoop是App持续运行的保证。

    Main函数中的RunLoop

    我们看一下,如果没有runloop:

    // 没有runloop循环,启动程序,打印出 Hello, World!后,程序马上退出
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSLog(@"Hello, World!");
        }
        return 0;
    }
    
    
    • 当Main函数执行到UIApplicationMain时,就开启了RunLoop运行循环
    • 在运行循环开启时,就会保证程序的持续运行并且处理App的各种事件,不会退出
    • Main函数中的RunLoop,被称为主运行循环,而主运行循环在整个App的生命周期中都不会被销毁,它是程序运行的保证
    //程序在启动时,第一步就会执行main函数,在main函数中会执行以下操作
    int main(int argc, char * argv[]) {
    @autoreleasepool {
        /*
         *nil:UIApplication类名或者子类名,如果为nil,就等于@"UIApplication"
         *NSStringFromClass([AppDelegate class]):UIApplication代理的名称
         */
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
      }
    }
    
    程序启动的完整流程
      1.执行main函数
      2.执行UIApplicationMain函数
         1> 指定UIApplication对象
         2> 指定UIApplication的代理
      3.创建UIApplication对象,并且指定他的代理
      4.创建一个事件循环:主循环(RunLoop),并且是一个死循环,保证程序的持续运行
      5.加载配置了所有应用程序信息的info.plist文件
          1> 判断 Main storyboard file base name 中有没有指定 Main,即需要加载的 Storyboard 文件
          2> 如果指定了就加载Main.storyboard文件
          3> 如果没有指定的话就会黑屏
      6.应用程序启动完毕
    

    RunLoops 是线程相关的基础框架的一部分。一个runloop 就是一个事件处理循环,用来不停地调度工作以及处理输入等事件。

    RunLoop 会再循环中处理App的各种事件,如 触摸事件,定时器事件,selector事件

    RunLoop最大的优势就是能节省CPU的资源,提高程序的性能,它会在需要执行任务的时候被唤醒,没有任务执行的时候进入休眠状态

    线程的生命周期存在五个状态:新建、就绪、运行、阻塞、死亡

    NSRunLoop 可以保持一个线程一直为活跃状态,不会被马上销毁。

    简单应用:

    //    1.获取主线程对应的RunLoop对象
    NSRunLoop *mainLoop = [NSRunLoop mainRunLoop];
    
    //    2.获取当前线程对应的RunLoop对象
    NSRunLoop *currentLoop = [NSRunLoop currentRunLoop];
    
    
    //    3.子线程中的RunLoop
    [NSThread detachNewThreadSelector:@selector(threadTask) toTarget:self withObject:nil];
    

    1.定时器:在主线程中设定一个一秒执行一次的定时器,能确保它每一秒都会执行一次吗?
    答案是否定的,主线程中事件很多,如果有一个事件堵塞了0.5秒,那么定时器就就会延迟0.5秒。所以一般是专门开启一个子线程运行添加定时器

    - (void)threadTask {
    /*子线程与RunLoop
     1.每一个子线程,都对应一个自己的RunLoop
     2.主线程的RunLoop在程序运行的时候就已经开启了,而子线程的RunLoop需要手动开启
     3.RunLoop需执行run方法,来开启,但如果RunLoop中没有任何任务,就会关闭
     */
    
    //自动添加到RunLoop中
    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(showTimer) userInfo:nil repeats:YES];
    

    默认加入了当前RunLoop的NSDefaultRunLoopMode模式

    //需要手动添加到RunLoop中
    NSTimer *timer2 = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(showTimer) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer2 forMode:NSRunLoopCommonModes];
    [[NSRunLoop currentRunLoop] run];
    
    RunLoopMode :

    default模式:几乎包括所有输入源(除NSConnection)
    NSDefaultRunLoopMode模式

    mode模式:处理modal panels

    connection模式:处理NSConnection事件,属于系统内部,用户基本不用

    event tracking模式:如组件拖动输入源 UITrackingRunLoopModes 不处理定时事件

    common modes模式:NSRunLoopCommonModes 这是一组可配置的通用模式。将input sources与该模式关联则同时也将input sources与该组中的其它模式进行了关联。

    例如:
    UIScrollView *scrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)];
    scrollView.backgroundColor = [UIColor orangeColor];
    scrollView.contentSize = CGSizeMake(0, SCREEN_HEIGHT*3);
    [self.view addSubview:scrollView];
    
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(showTimer) userInfo:nil repeats:YES];
    

    RunLoop一开始是NSDefaultRunLoopMode模式,在拖动scrollView的时候,RunLoop变成UITrackingRunLoopModes 模式,这时定时器不再执行,等到不再滑动,接着执行。这时可以将模式改为 NSRunLoopCommonModes ,这样在滑动的时候,定时器也是会执行的

    NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(showTimer) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    
    参考

    链接:http://www.jianshu.com/p/ccf979198271
    链接:http://www.jianshu.com/p/c3a0a183142a

    2018.7.19更新

    scheduledTimerWithTimeInterval: 方法会自动添加到runloop里,但是主线程和子线程的情况不一样:

    • 1>主线程:主线程的runloop在程序启动的时候就自动创建开启了,定时器加到runloop上就开始运行了;
    • 2>子线程:子线程的runloop是懒加载,只有调用获取runloop的方法(CFRunLoopGetCurrent()、[NSRunLoop currentRunLoop]),才会创建runloop,普通的一次性的任务执行结束后,线程就销毁了,不需要用到runloop,不需要调用方法创建。当一次任务执行结束后,希望该线程不被销毁,就需要开启runloop。开启runloop并正常运行需要满足两个条件:
      (1)要有有内容的mode(sourse或者timer);
      (2)需要[[NSRunLoop currentRunLoop] run]
      runloop里面没有mode,或者mode里面没有内容,或者没有调用run方法,runloop都不能正常开启。所以在子线程中调用scheduledTimerWithTimeInterval: 就是创建runloop并把定时器添加到runloop上,这时还需要调用 [[NSRunLoop currentRunLoop] run]来开启runloop

    注:runloop成功开启后就进入了循环,开启runloop后的代码就不会执行了。当结束runloop后(超出runloop设定最大时间或手动停止),开始执行后面的代码。

    何时使用 RunLoop

    我们应该只在创建子线程的时候,才显示的运行一个 RunLoopiOS app 会在应用启动的时候帮我run一个 runloop,而我们自己新建的子线程不会.
    对于子线程,我们仍然需要判断是否需要启动一个runloop,比如我们使用一个线程去处理一个预先定义的长时间的任务,我们应该避免启动runloop。下面是官方document 提供的使用 RunLoop 的几个场景:

    •   1.需要使用 Port-Based Input Source或者 Custom InputSource 和其他thread通讯时
    
    •   2.需要在线程中使用 Timer
    
    •   3.需要在线程中使用selector相关的方法(performSelecter:afterDelay: 、 performSelector:onThread:等方法)
    
    •   4.需要让线程周期性的执行某些工作
    

    参考的文章:
    runloop 入门:https://www.jianshu.com/p/2d3c8e084205
    runloop理解:https://www.jianshu.com/p/f33c0e5ad0e2
    runloop深入理解:https://blog.ibireme.com/2015/05/18/runloop/

    相关文章

      网友评论

        本文标题:iOS与NSRunLoop

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