深入了解RunLoop及在开发中的应用
一.RunLoop定义
RunLoop:运行循环,简单的说就是解决线程事件和管理线程的一种机制。当子线程的事件结束时,runloop将会自动休眠,app主线程中的runloop处于一直唤醒状态。当客户触发事件时,runloop通知线程执行事件内容。
二.线程与RunLoop的关系
1.每条线程都有唯一的一个与之对应的RunLoop对象,没有线程,也就没有RunLoop存在的必要。
2.RunLoop在第一次获取时创立,在线程结束时销毁;只能在一个线程的内部获取其 RunLoop(主线程除外)。
3.主线程的RunLoop系统默认启动,子线程的RunLoop需要主动开启;
有时候我们感觉自己在实际开发中很少用到RunLoop,其实在我们每次建立项目的时候,就已经使用上了RunLoop。
在程序的启动入口 main 函数中有这样一段熟习的代码:
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}
UIApplicationMain 函数内部就启动了一个与主线程相关联的 RunLoop。
当我们点击运行,系统运行 UIApplicationMain 函数,系统进入了:主线程 main 的运行循环。RunLoop 使得主线程一直处在运行循环中。
我们可以这样测试下UIApplicationMain的作用,用下面代码代替:
int main(int argc, char * argv[]) { @autoreleasepool { NSLog(@"启动"); return 0; }}
结果: 程序打印出“启动”后,就直接关闭了,控件与其余程序有关的都没有执行,界面空白,这说明了在 UIApplicationMain 函数中,开启了一个和主线程相关的 RunLoop,让 UIApplicationMain 不会返回,一直在运行中,也就保证了程序的持续运行。这就是为什么App程序启动之后能够持续运行在前端的起因。
三. RunLoop 对象和相关类
iOS中有2套API来访问和使用RunLoop:
Foundation:NSRunLoop
[NSRunLoop currentRunLoop]; // 取得当前线程的RunLoop对象[NSRunLoop mainRunLoop]; // 取得主线程的RunLoop对象
Core Foundation:CFRunLoopRef
CFRunLoopGetCurrent(); // 取得当前线程的RunLoop对象CFRunLoopGetMain(); // 取得主线程的RunLoop对象
文档中的相关类:
CFRunLoopRefCFRunLoopSourceRefCFRunLoopTimerRefCFRunLoopModeRefCFRunLoopObserverRef`
他们的关系如下图:
关系图
- 一个RunLoop包含若干个Mode,而每个Mode又包含若干个Source/Timer/Observer。
- RunLoop每次只能指定一种Mode。而且假如需要切换 Mode,只能退出当前 Loop。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
- 假如一个 mode 中一个 “Source/Timer/Observer” 都没有,则 RunLoop 会直接退出,不进入循环。
CFRunLoopSourceRef 输入源
是事件产生的地方,函数调用栈上Source有两个版本:Source0 和 Source1。
- Source0:非基于端口port,例如触摸,滚动,selector选择器等客户触发的事件;(只包含了一个回调函数,它并不能主动触发事件)
- Source1:基于端口port,少量系统事件; (包含了一个 mach_port 和一个回调函数,被用于通过内核和其余线程相互发送消息。能主动唤醒 RunLoop 的线程)
CFRunLoopTimerRef 定时源
基于时间的触发器,与NSTimer可混用。
包含了一个时间长度和一个回调函数。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
创立定时器源有两种方法,
方法一:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:4.0 target:self selector:@selector(backgroundThreadFire:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timerforMode:NSDefaultRunLoopMode];
方法二:
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(backgroundThreadFire:) userInfo:nil repeats:YES];
CFRunLoopModeRef mode类型
事实上CFRunLoopModeRef 类并没有对外暴露,而假如在Xcode中查看CFRunLoopRef,可以看到CFRunLoopModeRef 类,通过 CFRunLoopRef 的接口进行了封装。
CFRunLoopModeRef有5种形式:
kCFRunLoopDefaultMode 默认模式,通常主线程在这个模式下运行;UITrackingRunLoopMode 界面跟踪Mode,用于追踪Scrollview触摸滑动时的状态;kCFRunLoopCommonModes 占位符,带有Common标记的字符串,比较特殊的一个mode;UIInitializationRunLoopMode 刚启动App时进入的第一个Mode,启动后不在使用;GSEventReceiveRunLoop 内部Mode,接收系事件。
从关系图,我们可以知道 RunLoop 一次只能指定一种 Mode,且能够让不同组的 Source/Timer/Observer 互不影响.
CFRunLoopObserverRef 观察者
RunLoop的观察者,能够监听RunLoop的状态改变。
每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化,可以观察到不同时刻的状态有以下几个:
/* Run Loop Observer Activities */typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 即将进入LoopkCFRunLoopBeforeTimers = (1UL << 1), // 即将解决 TimerkCFRunLoopBeforeSources = (1UL << 2), // 即将解决 Source kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠 kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒 kCFRunLoopExit = (1UL << 7), // 即将退出Loop};
四、在实际开发中的应用
(1). 控制线程生命周期(线程保活)
在项目中,有时我们需要创立子线程,由于假如把所有的事情都放在主线程中去做,就会阻塞住主线程。导致APP 看起来很卡。这个时候即可以开启一个子线程,把耗时的操作放到子线程中。子线程做完事情以后,就会销毁。有时我们不希望子线程大量的创立和销毁,即可以使用 RunLoop 控制子线程的生命周期。
-(void)tapBtn{ NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(todo) object:nil]; [thread start];}-(void)todo{ NSLog(@"执行此方法"); NSLog(@"%@", [NSThread currentThread]);}
每一次点击按钮的时候,线程执行完方法,直接释放掉了,下一次直接创立了一个新的线程
使用 RunLoop 控制子线程保活
/** 线程对象 */@property(strong,nonatomic) NSThread *thread;@end@implementation MyPurseViewController- (void)viewDidLoad { [super viewDidLoad]; UIButton *btn = [[UIButton alloc]initWithFrame: CGRectMake(50, 150, 200, 80)]; btn.backgroundColor = [UIColor redColor]; [btn addTarget:self action:@selector(tapBtn) forControlEvents:UIControlEventTouchUpInside]; [btn setTitle:@"点击测试线程" forState:UIControlStateNormal]; [self.view addSubview:btn]; _thread = [[NSThread alloc]initWithTarget:self selector:@selector(todo) object:nil]; [_thread start]; // Do any additional setup after loading the view.}-(void)tapBtn{ NSLog(@"%@", [NSThread currentThread]);}-(void)todo{ [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run];}
(2). 处理NSTimer在UIScrollView滑动时中止工作的问题
默认情况下,在滚动 tableView、UIScrollView 的时候,NSTimer会中止工作,这是由于在滚动时,RunLoop 会进入另一个Mode 模式UITrackingRunLoopMode 下,在该模式下,定时器就会中止,当不在滚动 UITextView , 定时器会重新开始。 RunLoop 同一时间,只能运行一种模式。
例如:UIScrollView+ NSTimer演示滚动时,定时器中止工作
- (void)viewDidLoad { [super viewDidLoad]; UIScrollView *scrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight)]; [self.view addSubview:scrollView]; scrollView.backgroundColor = [UIColor redColor]; scrollView.contentSize = CGSizeMake(0, 3000); static int count = 0; [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"该方法第%d次",++count); }];}
从后端打印日志看出,当我们在手机屏幕上滑动时,定时器不工作,日志不打印,放开手后,定时器重新工作,开始打印。(应用最常见的应该为轮播图自动播放时)
边滚动,定时器边工作,我们即可以用NSRunLoop的默认模式:
static int count = 0; NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"该方法第%d次",++count); }]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
(3). 监控应用卡慢
有时我们在滑动列表时,感觉很卡,特别是列表上有很多图片要显示时,如何处理卡慢呢,由于我们现在加载图片用的SDWebImage,源码中已经解决了该问题,所以有时我们滑动列表时很顺畅。
(4). 性能优化
一个RunLoop对应一个线程
建议每一次启动RunLoop的时候,包装一个自动释放池,临时创立了很多对象,等着我们释放,在很多优秀的开源库中,都有这个说明
- (void)viewDidLoad { [super viewDidLoad]; _thread = [[NSThread alloc] initWithTarget:self selector:@selector(todo) object:nil]; [_thread start];}- (void) todo{// 该方法默认不加入RunLoop中,使用schedule可以 @autoreleasepool { NSTimer *timer = [NSTimer timerWithTimeInterval:0.3 target:self selector:@selector(test2) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run]; }}
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 深入了解RunLoop及在开发中的应用