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
我们应该只在创建子线程的时候,才显示的运行一个 RunLoop
。 iOS 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/