RunLoop是通过系统内部维护的事件循环让线程随时处理事件但并不退出的一种机制。可以做到有事件或者消息的时候由内核态切换到用户态,没事做的时候,由用户态切换到内核态。并且在内核态不占用系统资源.通常所说的RunLoop指的是NSRunloop或者CFRunloopRef,CFRunloopRef是纯C的函数,而NSRunloop仅仅是CFRunloopRef的OC封装。
1.RunLoop 与线程的关系
线程和RunLoop之间是一一对应的,主线程RunLoop自动开启,子线程RunLoop需要手动开启。
RunLoop的创建时发生在第一次获取时,销毁是发生在主线程结束时。
RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
2. RunLoop-Mode
RunLoop有五种Mode,Runloop 在某个时刻只能跑在一个 Mode 下,处理这一个 Mode 当中的 Source,Timer 和 Observer。
苹果文档中提到的 Mode 有五个,分别是:
* NSDefaultRunLoopMode
* NSConnectionReplyMode
* NSModalPanelRunLoopMode
* NSEventTrackingRunLoopMode
* NSRunLoopCommonModes
3.RunLoop-Source
* Source1—基于Port的线程间通信
* Source0—触摸事件和PerformSelectors会触发Source0
* Timers—定时器
* Observer—监听器,用于监听RunLoop的状态
CFRunLoopObserver可以观察的状态有如下6种:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即将进入run loop
kCFRunLoopBeforeTimers = (1UL << 1), //即将处理timer
kCFRunLoopBeforeSources = (1UL << 2),//即将处理source
kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),//被唤醒但是还没开始处理事件
kCFRunLoopExit = (1UL << 7),//run loop已经退出
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
有关RunLoop常见问题:
1.scrollView滑动导致定时器暂停的问题
> 其实就是runloop的mode在作怪,scrollView滚动的时候,当前的mainrunloop处于Trackingmode模式下,是不会处理defaultMode的消息,要解决这个问题可以将timer添加到commonModes来解决。
2.如何实现一个常驻线程
> 为当前线程开启一个RunLoop,然后向RunLoop中添加一个Port/Source等维持RunLoop的事件循环,最后启动RunLoop。
注意:创建子线程相关的RunLoop,在子线程中创建即可,并且RunLoop中要至少有一个Timer或一个Source保证RunLoop不会因为空转而退出。
3.怎样保证子线程数据返回更新UI的时候不打断用户的滑动操作?
> 可以把更新UI的这部分逻辑放到Default模式下,当用户滑动的时候是处于TrackingMode模式,所以不会打断,当用户停止滑动,主线程回到DefaultMode模式刷新UI。
4.Timer和Source也是一些变量,需要占用一部分存储空间,所以要释放掉。那么什么时候释放怎么释放呢?
> RunLoop内部有一个自动释放池,当RunLoop开启时,就会自动创建一个自动释放池,当RunLoop在休息之前会释放掉自动释放池的东西,然后重新创建一个新的自动释放池,当RunLoop重新被唤醒,Timer和Source等新事件会添加到新的自动释放池,RunLoop退出的时候也会被释放。
注意:只有主线程的RunLoop会默认启动,也就意味着会自动创建自动释放池,子线程需要在线程调度方法中手动添加自动释放池。
5.PerformSelector:afterDelay:这个方法在子线程中是否起作用?为什么?怎么解决?
不起作用,子线程默认没有 Runloop,也就没有 Timer。
解决的办法是可以使用 GCD 来实现:Dispatch_after
PerformSelector 的实现原理?
当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
例如:
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
[self performSelector:@selector(test) withObject:nil afterDelay:10];
NSLog(@"3");
});
NSLog(@"4");
- (void)test
{
NSLog(@"5");
}
答案是1423,test方法并不会执行。 原因是如果是带afterDelay的延时函数,会在内部创建一个 NSTimer,然后添加到当前线程的RunLoop中。也就是如果当前线程没有开启RunLoop,该方法会失效。 那么我们改成:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
[[NSRunLoop currentRunLoop] run];
[self performSelector:@selector(test) withObject:nil afterDelay:10];
NSLog(@"3");
});
test还是不会执行。原因是如果RunLoop的mode中一个item都没有,RunLoop会退出。即在调用RunLoop的run方法后,由于其mode中没有添加任何item去维持RunLoop的时间循环,RunLoop随即还是会退出。 所以我们自己启动RunLoop,一定要在添加item后
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
[self performSelector:@selector(test) withObject:nil afterDelay:10];
[[NSRunLoop currentRunLoop] run];
NSLog(@"3");
});
网友评论