iOS RunLoop基础概念

以下是我对runloop的一些基础认识的总结

1 每个线程都有一个自动创建好的runloop,但是只有主线程的runloop是默认开启的,其他子线程需要调用

NSRunLoop *runloop =[NSRunLoop currentRunLoop]; 所以Runloop 是不可主动创建的只能获取

它的启动方式一共有三种:

  • Unconditionally
  • With a set time limit
  • In a particular mode

这三种进入方式分别对应了三种方法:

  • run
  • runUntilDate
  • runMode:beforeDate:
以上三种开启方法的关系如下:

 run方法的本质就是无限调用 runMode:beforeDate:方法,同样地,runUntilDate:也会重复调用 runMode:beforeDate:,区别在于它超时后就不会再调用。

总结来说,runMode:beforeDate:表示的是 runloop的单次调用,另外两者则是循环调用。


它的关闭方式:

runloop 并没有可以调用的api 比如 stop之类的可以终止。所以它的关闭方式就是在开启的时候设置的 runUntilDate, runMode:beforeDate:,在某个时间结束。

所以如果你使用方法 run 去开启那你就不能手动去关闭这个runloop了,只能等到它所在的线程死掉然后它也跟着死掉。


另外推荐一对 开启关闭runloop的API 

CFRunLoopRun()启动 runloop

可以通过 CFRunLoopStop()方法结束。

注意 CFRunLoopStop() 是不能关闭 用run方法开启的runloop

因为

CFRunLoopStop()方法只会结束当前的 runMode:beforeDate:调用,而不会结束后续的调用,是因为上述说的  run 方法的本质就是无限调用 runMode:beforeDate: 方法。


以下是运用到runloop原理的地方

  • NSTimer计时器完全依赖于runloop
  • UIEvent事件的产生到分发给代码都是通过runloop
  • Autorelease自动释放也是在runloop跑完一圈后
  • NSObject(NSDelayedPerforming) performSelector,cancel
  • NSObject(NSThreadPerformAddition) performSelectorOnMainThread,performSelectorOnBackgroundThread
  • CA层的CADisplayLink(每画一帧会有一个回调),CATransition,CAAnimation
  • dispatch_get_main_queue()
  • NSURLConnection
  • AFNetworking,它的delegate跟网络传输数据都是在它的runloop里面执行的
  • NSPort描述通讯信道的抽象类

当然还有其它很多的地方,我就不列举了。
下面是一个和runloop相关的很经典的面试题:
最电商的app中会有一个功能,在一系列cell中会有很多定时器来运行,显示这个商品还有多长时间就开始销售。但是如果滑动这个tableview 就会发现这些timer 不动了
原因是:
runloop 常用的有以下两种模式的

 NSDefaultRunLoopMode

 NSRunLoopCommonModes 

其它模式

GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode(比如绘图),通常用不到 

 UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用 在appdelegate 打印出的当前loop 就是这个


第一种模式是默认的,NSTimer 计时器完全依赖于runloop的,如果希望界面滑动的时候不受影响那就把timer加入到另一个模式里面。

[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];

UITrackingRunLoopMode是runloop的滑动模式滑动的时候系统会把当前runloop从NSDefaultRunLoopMode切换到UITrackingRunLoopMode。

NSRunLoopCommonModes 其实是一个数组包含了

NSDefaultRunLoopMode

UITrackingRunLoopMode。

一个runloop只能又一个mode,当runloop切换model的时候其实是杀死了当前的runloop重新生成一个新的runloop。
runloop下面的几个类:
CFRunloopTimerRef // NSTimer 就是基于这个封装的
CFRunloopSourceRef  事件源(输入源)

事件产生的地方,分为Source0Source1两种

  • Source0: 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件

  • Source1: 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程


kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode

CFRunloopObservorRef // 观察者监听runloop状态改变

以下是runloop 保活线程的运用:
AFNetworking的经典代码, 3.0版本以下的代码
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
     @autoreleasepool {
         [[NSThread currentThread] setName:@ "AFNetworking" ];
         NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
         [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
/*

通常情况下,调用者需要持有这个 NSMachPort (mach_port) 并在外部线程通过这个 port 发送消息到 loop 内;
但此处添加 port 只是为了让 RunLoop 不至于退出,并没有用于实际的发送消息

*/
         [runLoop run];
     }
}
  
+ (NSThread *)networkRequestThread {
     static NSThread *_networkRequestThread = nil;
     static dispatch_once_t oncePredicate;
     dispatch_once(&oncePredicate, ^{
         _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
         [_networkRequestThread start];
     });
     return  _networkRequestThread;
}


因为这个数据的处理是线程完成任务之后的事情,如果任务完成线程死掉这是不行的,AFNetworking 是这样保活线程的。








你可能感兴趣的:(IOS)