RunLoop总结

2021-01-28  本文已影响0人  生产八哥

RunLoop基础概念

通俗的来说,RunLoop就是一个带有判断条件do-while循环,不会一直消耗CPU,是一种闲等待,可以唤醒和休眠,保持程序的持续运行,处理App中各种事务,在状态为StopFinish时退出。
OSX/iOS 系统中,提供了两个RunLoop对象:NSRunLoopCFRunLoopRef

如果想深入研究的话,可以下载源码

通过源码和调试可以得知以下几个知识点

  1. 在获取线程的runloop时,以线程为key,runloop为value保存到一个CFDictionary中,可以说明,线程和runloop是一一对应的。
  2. 保证线程不退出,则要往线程里加runloop。保证runloop不退出,则要往里面加timer、souce0、source1。注意:source0可以保证子runloop不退出,但不能唤醒runloop
  3. 主线程的runloop是系统创建好的,并用一个静态static变量保存,所以一直存在,保存在一个_CFTSDTable里。所以GetCurrentRunloop时候子线程才会去创建子Runloop。
  4. 保证runloop不退出的判读里,主runloop不需要判断里面是否添加了timer、source、observer。所以主runloop和子runloop是分开判断的。
  5. 唤醒runloop的条件有:timer、source1、手动CFRunLoopWakeUp、超时(会短暂唤醒立马再退出)
  6. AFNetworking2.0创建常驻线程的原理就是往一个子runloop中添加source1,即[NSMarchPort port].
  7. mach port是用来跨线程通讯的,可以发送消息message。例如:剪切板:剪切板的内容每个App都可以访问。
  8. runloop休眠后,会被一个接受消息mach_msg的mach port阻塞掉,以阻止CPU消耗, runloop唤醒或者添加timer本质都是通过mach port去取消掉block的线程。
  9. performSelector:withObject:afterDelay 依赖于线程的 runloop,因为它本质上是由一个定时器负责定期加入到 runloop 中执行。
  10. run 方法的文档还可以知道,它的本质就是无限调用 runMode:beforeDate: 方法,那么在run方法的下面的操作代码都不会被执行到。同样地,runUntilDate: 也会重复调用 runMode:beforeDate:,区别在于它超时后就不会再调用。总结来说,runMode:beforeDate: 表示的是 runloop 的单次调用,即唤醒过一次就退出了,不会再次唤醒,另外两者则是循环调用。想从 runloop 里面退出来,就不能用 run 方法。根据实践结果和文档,另外两种启动方法也无法手动退出,因为CFRunLoopIsStopped的是众多循环中的一次而已。
  11. NSRunLoopCommonModes是一个伪模式。Runloop的run方法内部其实大概就是不断调用:runMode:beforeDate:.如果这里的mode传commonMode,则会立马返回Finished,停止run,也就是无法保活线程。因为commonMode相当于是default和tracking伪合集,而不是具体的一个mode,所以不能run。runloop的addTimer:timer forMode可以设置commonMode,因为底层代码会去查找default和tracking模式。``
//前提是当前Runloop有timers或sources时保活了runloop,执行完不会退出
[sonLoop run];  //循环运行,此方法下的代码不被执行
[sonLoop runUntilDate:[NSDate distantFuture]];  //同上
[sonLoop runUntilDate:[NSDate date]];  //sonLoop立马退出
[sonLoop runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]]; //立马退出,因为commonMode是个伪模式
[sonLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; //线程只能唤醒一次,之后就退出,并且执行此行代码之后的代码,因为此方法是单循环,不同于上面两个方法的重复循环
[sonLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10.0]];  //10秒后自动退出,10秒内唤醒一次

Runloop和GCD的关系

  1. RunLoop 的超时时间就是使用 GCD 中的 dispatch_source_t来实现的。
  2. 当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并执行。GCD中将任务提交到主线程的主队列即dispatch_get_main_queue()时,这里的任务是由RunLoop负责执行。只有主队列的任务会交由RunLoop对象处理,其他队列的则由GCD自行处理。

Runloop和AutoreleasePool的关系

AutoreleasePool

SDWebImage中,由于encodedDataWithImage会把image解码成data,可能造成内存暴涨,所以加autoreleasepool避免内存暴涨。


RunLoop和performselector

利用PerformSelector设置当前线程的RunLoop的运行模式
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"tupian"] afterDelay:4.0 inModes:@[NSDefaultRunLoopMode]];
Source事件源

IOKit 是硬件驱动程序的运行环境,包含电源、内存、CPU 等信息。
我们触摸屏幕,先摸到硬件(屏幕),屏幕表面的事件会被IOKit先包装成Event,通过mach_Port传给正在活跃的APP , Event先告诉source1(mach_port),source1唤醒RunLoop, 然后将事件Event分发给source0,然后由source0来处理。

RunLoopObserver

如果想要观察runloop在程序运行中各种状态如何运作的,可以添加观察者观察runloop的各种状态切换。

CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                                  kCFRunLoopAllActivities,
                                                  YES,
                                                  0,
                                                  &runLoopObserverCallBack,
                                                  &context);
//将观察者添加到主线程runloop的common模式下的观察中
CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);

回调函数

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
    switch (activity) {
        case kCFRunLoopBeforeWaiting:
            NSLog(@"RunLoop休眠");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"RunLoop唤醒");
            break;
        case kCFRunLoopExit:
            NSLog(@"RunLoop退出");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"RunLoop处理事件源");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"RunLoop处理定时器");
            break;
        default:
            break;
    }
}

本文只是对本人印象笔记的总结,因为已经有许多优秀的文章了,并没有对很多细节展开。若有不对的地方,还请欢迎指出纠正,谢谢。

上一篇 下一篇

猜你喜欢

热点阅读