3 如果没有事件发送,会让程序进入休眠状态
Runloop 的两种模式: NSDefaultRunLoopMode+ NSRunLoopCommonModesNSRunLoopCommonModes:用户交互
来看看这段代码~思考下:以下代码的输出是什么?
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"来了");
nil 就相当于UIApplication
int a = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"come here");
return a;
}
}
想好了吗?揭晓答案了哦~答案是“来了”,你猜对了吗?why?因为UIApplicationMain 有个死循环(runloop循环)
死循环的目的何在?
- 保证去程序不退出
- 负责监听事件,触摸,时钟,网络事件
- 如果没有事件发生,会让程序进入休眠状态(也会消耗cpu的性能)
Runloop 的五种模式
1.UITrackingRunLoopMode UI模式
2.NSDefaultRunLoopMode 默认模式
3.NSRunLoopCommonModes 占位模式相当于 UI模式+ 默认模式
4.UIInitializationRunLoopMode:在刚启动App时进入的第一个Mode,启动完成后不再使用
5.GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到
有这样一个场景 :界面上有个textview,当滚动textview,viewdidload 里的timer就不工作了
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeMethod) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
我们来解释下原因,请看下图
此时的timer在默认模式下,苹果的一贯注重用户体验,所以当滚动textview (用户操作UI)的时候,runloop 会优先处理UI模式下的source。如果这时吧计时器加入到UI模式下,timeMethod 会正常进行,但当用户停止操作UI,那么UI模式下的runloop 就会进行休眠,所以timeMethod也会停止运行。此时我们可以吧timer加入到UI模式+默认模式。这时苹果为了优化,产生了一个NSRunLoopCommonModes 占位模式,但是它并不属于Runloop的五大模式之一。
RunLoop && 多线程
- (void)viewDidLoad {
[super viewDidLoad];
NSThread *thread = [[NSThread alloc]initWithBlock:^{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeMethod) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
NSLog(@"来了1");
}];
[thread start];
}
- (void)timeMethod {
NSLog(@"来了2");
[NSThread sleepForTimeInterval:1];
NSLog(@"%@",[NSThread currentThread]);
}
2018-05-16 10:05:46.915347+0800 测试完[841:26856] 来了1
原因:线程走了,想要保证线程的命,就要让线程有执行不完的任务,线程就不会释放了~
- (void)viewDidLoad {
[super viewDidLoad];
NSThread *thread = [[NSThread alloc]initWithBlock:^{
NSTime*timer= [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeMethod) userInfo:nilrepeats:YES];
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
//这个循环只能保证线程的命,但是timeMethod 还是不能调用
// while(true)
//{
//从事件队列中取出事件来处理!!(但是平没有开源这个,但是封装了一个NSRunLoop 来处理这个事件)
// }
//RunLoop -- 一条线程上面的RunLoop模式是不循环的
//CFRunLoop -- currentRunLoop () 第一次获取RunLoop的时候,创建RunLoop
[[NSRunLoop currentRunLoop]run];//死循环
NSLog(@"来了1");
}];
[thread start];
}
- (void)timeMethod {
NSLog(@"来了2");
[NSThread sleepForTimeInterval:1];
NSLog(@"%@",[NSThread currentThread]);
}
打印结果如下:
2018-05-11 10:06:44.594863+0800 RunloopTest[1300:42091] 来了2
2018-05-11 10:06:45.599998+0800 RunloopTest[1300:42091]
主线程与子线程做的事情都是一样的,唯一的区别是:UI界面在主线上面的;App启动的第一条线程
苹果为啥要吧UI界面放在主线程?为了安全+效率
三,CFRunloop 的一个用法
#import "ViewController.h"
typedef void(^RunloopBlock)(void);
@interface ViewController ()
@property(strong,nonatomic)NSMutableArray *tasks;
@end
@implementation ViewController
//这里在cell 上加载图片的耗时操作没写
/*分析卡顿的原因:
所有的Cell的加载都在主线程的一次Runloop循环中,UI渲染也属于Runloop的事情,但是一次渲染18张图片,渲染太多。导致卡顿
解决思路:一次Runloop循环,只加载一张图片
步骤:
1.观察(observer)Runloop的循环
2.一次Runloop循环,加载一张图片
|-Cell加载图片的方法放到数组里
|-Runloop循环 一次,就从数组取出一个图片加载
*/
- (void)viewDidLoad {
[self addRunloopObserver];
self.tasks = NSMutableArray.array;
[self addTask:^{
}];
}
- (void)addTask:(RunloopBlock)task{
//保存任务到数组
[self.tasks addObject:task];
if (self.tasks.count == 0) {
return;
}
if (self.tasks.count>18) {
[self.tasks removeObjectAtIndex:0];
}
}
- (void)addRunloopObserver{
//获取当前的runloop
CFRunLoopRef runloop = CFRunLoopGetCurrent();
//定义上下文
/*
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
} CFRunLoopObserverContext;
*/
CFRunLoopObserverContext context = {
0,
(__bridge void *)self,
&CFRetain,
&CFRelease,
NULL
};
//创建观察者
//c 中create new copy 堆区开辟内存空间。需要释放
CFRunLoopObserverRef runLoopObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, 0, &callback, &context);
CFRunLoopAddObserver(runloop, runLoopObserver, kCFRunLoopCommonModes);
//释放
CFRelease(runloop);
}
void callback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
ViewController *vc = (__bridge ViewController *)info;
if (vc.tasks.count == 0) {
return;
}
RunloopBlock task = vc.tasks.firstObject;
task();
[vc.tasks removeObjectAtIndex:0];
};