runLoop称之为运行循环。我们知道应用程序刚启动的时候就会自动调用main函数,然后main函数中直接返回return,从代码层面上理解,既然renturn了,我们程序为什么一直没有退出呢?这就是runLoop做的事了,开启了一个runLoop,相当于一个死循环,保证了程序不退出。return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
runLoop可以运用在 Timer(NSTimer)、Source(按照函数调用栈方式分为source0和source1,source1内核事件,系统底层事件,souce0非source1事件,例如触摸事件touchesBegan。source可以运用在GCD中)上、 Obsever(CFRunLoopObserver)上。
runLoop负责处理事件: a.触摸事件(用户操作UI界面响应的相关事件)。b.时钟事件。例如NSTimer。c.网络事件。runLoop可以这样理解,当没有事件做的时候,处于睡眠状态。当有事件处理时又处于活跃状态,处理完事件后又处于睡眠状态这样响应模式。
runLoop模式有:
UITrackingRunLoopMode UI模式 处理UI事件
UIDefaultRunLoopMode 默认模式 苹果建议处理网络和时钟事件。
NSRunLoopCommonModes 占位模式 同时运行在UI模式和默认模式
优先级:UI模式 >默认模式。
通过上面的介绍,觉得runLoop比较好理解,但是用起来还是挺复杂的,因为runLoop与多线程有密不可分关系,runLoop用在多线程上面会有些难度。每一条线程都要一个runLoop。当你开启一个线程时,如果你不运行runLoop,线程就会被回收,线程里面做的相关处理就不会执行。所以如果需要在线程中保持相关的事件处理,那么线程中runLoop应处理运行状态,一旦runLoop失去了运行状态,线程中相关时钟操作就不会执行了。
说这么多,下面我们结合代码分别介绍runLoop运用在 Timer(NSTimer)、Source(例如GCD)上、 Obsever(CFRunLoopObserver)上。
一、runLoop时钟事件上运用。
NSTimer * timer = [NSTimertimerWithTimeInterval:1.0target:selfselector:@selector(updataTimer)userInfo:nilrepeats:YES];
[[NSRunLoopcurrentRunLoop]addTimer:timerforMode:NSDefaultRunLoopMode];
- (void)updataTimer {
staticint num =0;
[NSThreadsleepForTimeInterval:1.0];
NSLog(@"%@ %d",[NSThreadcurrentThread],num++);
}
这样我们通过RunLoop就启动了timer,但是我们会发现,当操作UI界面上的控件时,定时器的方法就没有执行了,什么原因呢?原来是由于当你操作UI界面时,runLoop去处理UI界面事件,所以时钟事件runLoop就没有处理啦,那么想要操作UI的同时,时钟事件也同时响应。这就用到了我们前面介绍的runLoop模式,当添加timer时,这样写:
[[NSRunLoopcurrentRunLoop]addTimer:timerforMode:NSRunLoopCommonModes];//那么runLoop执行UI同时执行时钟事件,这样就满足操作UI的同时,时钟事件也同时响应啦
二.runLoop的source(关于线程)使用。
每一条线程中runLoop默认是没有关闭的,所以我们需要手动开启runLoop(记住:要想保住一条线程,必须开启的他的runLoop)
下面直接代码复制参考:
@interface ViewController (){
NSThread *_thread;
BOOL _finishd;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[superviewDidLoad];
_finishd =NO;
NSThread *thread = [[NSThreadalloc]initWithBlock:^{
NSTimer *timer = [NSTimertimerWithTimeInterval:1.0target:selfselector:@selector(timerMethod)userInfo:nilrepeats:YES];
[[NSRunLoopcurrentRunLoop]addTimer:timerforMode:NSRunLoopCommonModes];
while (!_finishd) {
[[NSRunLoopcurrentRunLoop]runUntilDate:[NSDatedateWithTimeIntervalSinceNow:0.001]];
}
}];
//启动线程
[thread start];
_thread = thread;
}
- (void)timerMethod {
NSLog(@"come here");
[NSThreadsleepForTimeInterval:1.0];
staticint num;
num ++;
NSLog(@"%d---%@",num,[NSThreadcurrentThread]);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
_finishd =YES;
}
- (void)didReceiveMemoryWarning {
[superdidReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
测试得出结果:
当我们没有点击屏幕没有执行touchesBegan方法时,runLoop每0.001秒执行一次,_finishd一直为零,runLoop一直执行。
当我们点击屏幕时候_finishd为YES,!_finishd为真,runLoop就不再执行了,线程中的时钟事件就不再执行。
补充一下runLoop在source中运用,即GCD中运用。
首先在viewDidLoad中已经写了三个变量:
__weak IBOutletUILabel *numberLabel;
dispatch_source_t _timer;
int outTime;
我们快速的创建的一个GCD定时器
dispatch_queue_t queue =dispatch_get_global_queue(0,0);
dispatch_source_t timer =dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0,0, queue);
_timer = timer;
dispatch_source_set_timer(_timer,dispatch_walltime(NULL,0),1.0 *NSEC_PER_SEC,0);//每秒执行
dispatch_source_set_event_handler(_timer, ^{
NSLog(@"++++%d",outTime);
if (outTime <=0) {
dispatch_source_cancel(_timer);
dispatch_async(dispatch_get_main_queue(), ^{
numberLabel.text =@"0";
});
} else {
NSString *timeString = [NSStringstringWithFormat:@"%d",outTime];
dispatch_async(dispatch_get_main_queue(), ^{
numberLabel.text = timeString;
outTime--;
});
}
});
dispatch_resume(_timer);
输出结果:
2017-10-25 16:34:35.381 GCDTest[4456:1058917] ++++10
2017-10-25 16:34:36.382 GCDTest[4456:1058917] ++++9
2017-10-25 16:34:37.382 GCDTest[4456:1058917] ++++8
2017-10-25 16:34:38.382 GCDTest[4456:1058917] ++++7
2017-10-25 16:34:39.382 GCDTest[4456:1058917] ++++6
2017-10-25 16:34:40.382 GCDTest[4456:1058917] ++++5
2017-10-25 16:34:41.382 GCDTest[4456:1058917] ++++4
2017-10-25 16:34:42.383 GCDTest[4456:1058917] ++++3
2017-10-25 16:34:43.382 GCDTest[4456:1058917] ++++2
2017-10-25 16:34:44.383 GCDTest[4456:1058917] ++++1
2017-10-25 16:34:45.383 GCDTest[4456:1058917] ++++0
我们发现代码中没有一句写关于runLoop的,但是输出结果的同时拖动UI不会卡,这是什么原因呢?我们上面不是说每一条线程中都有一个runLoop吗?原来原因是GCD是C书写的,GCD已经帮我们把runLoop相关工作做好了,不用的管理,这就是为什么GCD的好用指出,很多人都喜欢使用GCD。
三.runLoop的Obsever的使用。
/** 存放任务的数组 */
@property(nonatomic,strong)NSMutableArray * tasks;
/** 任务标记
*/
@property(nonatomic,strong)NSMutableArray * tasksKeys;
/** 最大任务数 */
@property(assign,nonatomic)NSUInteger max;
/** timer */
@property(nonatomic,strong)NSTimer * timer;
如果我们直接在cell添加imageView,[cell.contentView addSubview:imageView];发现拖动tableView的时候会非常卡。
那是什么原因导致tableView的卡动呢?原来一次runLoop循环需要渲染界面上显示的很多张高清的大图,需要耗很多时间,导致UI操作卡顿现象。
解决方案:我让一次runLoop循环只加载一张图片,runLoop就会变得很流畅,拖拽的时候响应事件就会变得很流畅,界面的渲染就会变得流畅,卡顿就不会变得那么明显。
首先我们说让一次runLoop循环只加载一张图片,那么就需要知道runLoop的每次循环。在viewDidLoad里面
[selfaddRunloopObserver];
//当然这里面都是C语言 --添加一个监听者
-(void)addRunloopObserver{
//获取当前的RunLoop
CFRunLoopRef runloop =CFRunLoopGetCurrent();
//定义一个centext
CFRunLoopObserverContext context = {
0,
( __bridgevoid *)(self),
&CFRetain,
&CFRelease,
NULL
};
//定义一个观察者
staticCFRunLoopObserverRef defaultModeObsever;
//创建观察者
defaultModeObsever = CFRunLoopObserverCreate(NULL,
kCFRunLoopBeforeWaiting,
YES,
NSIntegerMax -999,
&Callback,
&context
);
//添加当前RunLoop的观察者
CFRunLoopAddObserver(runloop, defaultModeObsever,kCFRunLoopCommonModes);
//c语言有creat就需要release
CFRelease(defaultModeObsever);
}
Callback这个函数是为addRunloopObserver方法提供回调的。
//MARK: 回调函数
//定义一个回调函数 一次RunLoop来一次
static void Callback(CFRunLoopObserverRef observer,CFRunLoopActivity activity,void *info){
ViewController * vc = (__bridgeViewController *)(info);
if (vc.tasks.count ==0) {
return;
}
BOOL result =NO;
while (result ==NO && vc.tasks.count) {
//取出任务
RunloopBlock unit = vc.tasks.firstObject;
//执行任务
result = unit();
//干掉第一个任务
[vc.tasksremoveObjectAtIndex:0];
//干掉标示
[vc.tasksKeysremoveObjectAtIndex:0];
}
}
最后我们在
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
方法中并不是直接添加在cell.contentView上添加imageView,而是定义一个含有block的一个方法,cell上添加图片放入这个block中执行,每一次runLoop循环执行一个block(这个block代码块就是往cell上天机imageView)。含有block的方法如下:
//MARK: 添加任务
-(void)addTask:(RunloopBlock)unit withKey:(id)key{
[self.tasksaddObject:unit];
[self.tasksKeysaddObject:key];
//保证之前没有显示出来的任务,不再浪费时间加载
if (self.tasks.count > self.max) {
[self.tasksremoveObjectAtIndex:0];
[self.tasksKeysremoveObjectAtIndex:0];
}
}
最后测试滑动tableView就不再像之前那么卡顿了,因为我们让一次runLoop循环执行一个block只加载一张图片。
注意的是runLoop的Obsever模式情况下,runloop对应的是CFRunLoopRef,是C语言的,监听关于runLoop执行的代码用C来写的。