iOS与NSRunLoop

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)