什么是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,保证滑动视图的流畅性 -
开启一个常驻线程
网友评论