二话不说先上我麦来压压惊
什么是RunLoop?
RunLoop顾名思义运行着的循环,而且是一个死循环,本质是一个do,while循环,他负责监听几乎所有的事件(触摸事件,网络事件,时钟事件),当监听到了事件就会唤醒线程去执行,没事件RunLoop的线程就休眠。
RunLoop常见的模式
RunLoop在同一时间中只能响应一种模式下的事件,同时会关闭上一种模式;不同的事件都有对应的模式,处理事件都会切换到事件对应的模式。
<ul>
<li> NSDefalutRunLoopMode //默认模式
<li> UITrackingRunLoopMode //拖动事件,例如:UIScrollView
<li> NSRunLoopCommonModes //占位模式,包含前面两个
RunLoop跟线程的关系
RunLoop跟线程是一一对应的,不能主动创建,只能通过获取来拿到RunLoop对象
-
在主线程中RunLoop是默认创建开启的,来保证线程不死,程序不退出;在分线程中,默认没有创建RunLoop,线程在执行完任务之后就会退出,当我们在线程中获取RunLoop时,它才会创建,一旦run起来,这个分线程将跟主线程一样不会主动退出。
开启一个线程,并创建一个定时器 NSThread *thread = [[NSThread alloc]initWithBlock:^{ NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"哈哈"); }]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; //1.[[NSRunLoop currentRunLoop] run]; //2.while (!_isFinished) { // [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.001]]; //} NSLog(@"你看,来了吗?"); }]; [thread start];
来看看运行结果:
发现定时器并没有执行回调方法!
原因:子线程中默认是不开启RunLoop的,所以线程里面代码执行完之后,线程就销毁了。
解决:让线程常驻 -> 开启RunLoop。直接run ->
[[NSRunLoop currentRunLoop] run];
但是在执行run方法之后,在当前线程中后面的代码将永远不会再执行,比如NSLog(@"你看,来了吗?");
,因为RunLoop会一直在那里循环,所以这个线程将永远不会再自动销毁,只能通过执行[NSThread exit]
来暴力退出,RunLoop也会随之关闭,当然线程中后续代码更加不会再执行。-
手动添加一个循环来控制RunLoop循环,
runUntilDate:
同run方法开启RunLoop,只不过是加了一个时间限制,在未来多少秒暂停,这样通过手动控制RunLoop循环就可以控制线程的生命周期,当关闭此循环,Runloop也会停止,然后会执行线程剩余代码,之后线程销毁,这样做的好处就是可控制。while (!_isFinished) { [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.001]]; }
RunLoop优化
RunLoop如何优化?我们都知道耗时操作放到子线程中执行,更新UI放到主线程执行(在子线程中更新UI也会等到子线程所有操作执行完了然后再到主线程中执行UI操作,因为UIKit是线程不安全的:UI控件都是用的原子属性->效率高。),那么如果更新UI耗时长怎么办?其实是因为RunLoop在一次循环当中执行了过多的复杂的UI操作,我们需要对UI操作做拆分,然后增加RunLoop的循环次数来分步执行,一次循环只做一个UI更新模块,从而保证RunLoop的流畅性。
- (void)addRunLoopObserver{
//添加RunLoop监听,需要使用CFRunLoop,因为NSRunloop没有这个功能
//1.获取runloop
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
//2.1创建上下文
CFRunLoopObserverContext context = {
0, //这个context的版本
(__bridge void *)(self), //传入的参数,这里我传入控制器
CFRetain, //告诉它retain是调用哪个函数
CFRelease, //告诉它release是调用哪个函数
nil,
};
//2.2创建runloop观察者
/*
CFRunLoopObserverRef CFRunLoopObserverCreate(CFAllocatorRef allocator,
CFOptionFlags activities,
Boolean repeats,
CFIndex order,
CFRunLoopObserverCallBack callout,
CFRunLoopObserverContext *context);
@param allocator:这个参数用来分配空间给新的对象。默认情况下使用NULL或者kCFAllocatorDefault。
@param activities:设置Runloop的运行阶段的标志,当运行到此阶段时,CFRunLoopObserver会被调用
@param repeats:CFRunLoopObserver是否循环调用,false为单词调用,否则循环调用。
@param order:CFRunLoopObserver的优先级,当在Runloop同一运行阶段中有多个CFRunLoopObserver时,根据这个来先后调用CFRunLoopObserver。正常情况下使用0。
@param callout:回调函数
@param context:CFRunLoopObserver结构体里面的一个结构体,它主要用来给回调函数传递消息的。
@return CFRunLoopObserverRef:观察者指针对象
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(NULL,
kCFRunLoopBeforeWaiting,
YES, 0,
callBack,
&context);
//3.给runloop添加观察者
CFRunLoopAddObserver(runLoop, observer, kCFRunLoopCommonModes);
//4.通过定时器来增加runloop循环次数
[NSTimer scheduledTimerWithTimeInterval:0.001
target:self
selector:@selector(timerAction)
userInfo:nil
repeats:YES];
}
- (void)timerAction{
//不执行任何代码,只作为一个触发runloop监听回调的功能。
}
/**
回调函数,在kCFRunLoopBeforeWaiting(runloop等待执行循环之前)情况下调用,因为定时器给的是0.001秒,所以这里调用非常频繁,保证一次循环执行一个任务
@param observer 观察者对象
@param activity Runloop的运行阶段的标志
@param info CFRunLoopObserverContext传入的参数
*/
void callBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
//1.拿到传过来的参数,再进行转换,因为这是C函数,不能直接调用self,所以在添加观察的时候把self传过来。
ViewController *vc = (__bridge ViewController *)(info);
//2.执行拆分之后的UI模块(拆分的模块用task数组装起来,具体任务包装在block中,在这里只要执行block即可)
if (vc.tasks.count == 0) {
return;
}
RunLoopBlock block = vc.tasks.firstObject;
!block?:block();
[vc.tasks removeObjectAtIndex:0];
}