当有持续的异步任务需求时,咱们会建立一个独立的生命周期可控的线程。RunLoop就是控制线程生命周期并接收事件进行处理的机制。php
RunLoop是iOS事件响应与任务处理最核心的机制,它贯穿iOS整个系统。编程
Foundation: NSRunLoopCore Foundation: CFRunLoop 核心部分,代码开源,C 语言编写,跨平台安全
经过RunLoop机制实现省电,流畅,响应速度快,用户体验好ruby
进程是一家工厂,线程是一个流水线,Run Loop就是流水线上的主管;当工厂接到商家的订单分配给这个流水线时,Run Loop就启动这个流水线,让流水线动起来,生产产品;当产品生产完毕时,Run Loop就会暂时停下流水线,节约资源。RunLoop管理流水线,流水线才不会由于无所事事被工厂销毁;而不须要流水线时,就会辞退RunLoop这个 主管,即退出线程,把全部资源释放。网络
RunLoop并非iOS平台的专属概念,在任何平台的多线程编程中,为控制线程的生命周期,接收处理异步消息都须要相似RunLoop的循环机制实现,Android的Looper就是相似的机制。多线程
主线程的RunLoop在应用启动的时候就会自动建立架构
其余线程则须要在该线程下本身启动框架
不能本身建立RunLoop异步
RunLoop并非线程安全的,因此须要避免在其余线程上调用当前线程的RunLoop函数
RunLoop负责管理autorelease pools
RunLoop负责处理消息事件,即输入源事件和计时器事件
主线程 (有 RunLoop 的线程) 几乎全部函数都从如下六个之一的函数调起:
CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
CFRunloop is calling out to an abserver callback function
用于向外部报告 RunLoop 当前状态的更改,框架中不少机制都由 RunLoopObserver 触发,如 CAAnimation
CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
CFRunloop is calling out to a block
消息通知、非延迟的perform、dispatch调用、block回调、KVO
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUECFRunloop is servicing the main desipatch queue
CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
CFRunloop is calling out to a timer callback function
延迟的perform, 延迟dispatch调用
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
CFRunloop is calling out to a source 0 perform function
处理App内部事件、App本身负责管理(触发),如UIEvent、CFSocket。普通函数调用,系统调用
CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
CFRunloop is calling out to a source 1 perform function
由RunLoop和内核管理,Mach port驱动,如CFMachPort、CFMessagePort
RunLoop 架构

RunLoop 运行时
主要有如下六种状态:
kCFRunLoopEntry -- 进入runloop循环
kCFRunLoopBeforeTimers -- 处理定时调用前回调
kCFRunLoopBeforeSources -- 处理input sources的事件
kCFRunLoopBeforeWaiting -- runloop睡眠前调用
kCFRunLoopAfterWaiting -- runloop唤醒后调用
kCFRunLoopExit -- 退出runloop
主线程App运行时
RunLoopObserver与Autorelease Pool的关系
UIKit 经过 RunLoopObserver 在 RunLoop 两次 Sleep 间对 Autorelease Pool 进行 Pop 和 Push 将此次 Loop 中产生的 Autorelease 对象释放。
RunLoop的挂起与唤醒
指定用于唤醒的 mach_port 端口
调用 mach_msg 监听唤醒端口,被唤醒前系统内核将这个线程挂起,停留在 mach_msg_trap 状态。
由另外一个线程向内核发送这个端口的msg后,trap状态被唤醒,RunLoop继续工做。
RunLoop
支持接收处理输入源(Input Source)事件,包括:
系统的Mach Port事件,是一种通信事件自定义输入事件
支持接受处理定时源(Timer)事件
在启动RunLoop以前,必须添加监听的输入源事件或者定时源事件,不然调用[runloop run]会直接返回,而不会进入循环让线程长驻。
若是没有添加任何输入源事件或Timer事件,线程会一直在无限循环空转中,会一直占用CPU时间片,没有实现资源的合理分配。没有while循环且没有添加任何输入源或Timer的线程,线程会直接完成,被系统回收。
//错误作法 NSRunLoop *runLoop = [NSRunLoop currentRunLoop];while (!self.isCancelled && !self.isFinished) { [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]]; };//正确作法NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];while (!self.isCancelled && !self.isFinished) { @autoreleasepool { [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]]; } }
理解Run Loop Mode就是流水线上支持生产的产品类型,流水线在一个时刻只能在一种模式下运行,生产某一类型的产品。消息事件就是订单。
Cocoa定义了四中Mode
Default:NSDefaultRunLoopMode,默认模式,在Run Loop没有指定Mode的时候,默认就跑在Default Mode下
Connection:NSConnectionReplyMode,用来监听处理网络请求NSConnection的事件
Modal:NSModalPanelRunLoopMode,OS X的Modal面板事件
Event tracking:UITrackingRunLoopMode,拖动事件
Common mode:NSRunLoopCommonModes,是一个模式集合,当绑定一个事件源到这个模式集合的时候就至关于绑定到了集合内的每个模式
RunLoop能够经过 [acceptInputForMode:beforeDate:]和[runMode:beforeDate:]来指定在一段时间内的运行模式。若是不 指定的话,RunLoop默认会运行在Default下(不断重复调用runMode:NSDefaultRunLoopMode beforDate:)
在主线程启动一个计时器Timer,而后拖动UITableView或者 UIScrollView,计时器不执行。这是由于,为了更好的用户体验,在主线程中Event tracking模式的优先级最高。在用户拖动控件时,主线程的Run Loop是运行在Event tracking Mode下,而建立的Timer是默认关联为Default Mode,所以系统不会当即执行Default Mode下接收的事件。解决方法:
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFireMethod:) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; //或 [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode]; [timer fire];
Run Loop主要有如下三个应用场景:
维护线程的生命周期,让线程不自动退出,isFinished为Yes时退出。
NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];while (!self.isCancelled && !self.isFinished) { @autoreleasepool { [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]]; } }
建立常驻线程,执行一些会一直存在的任务。该线程的生命周期跟App相同
@autoreleasepool { NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; }
在必定时间内监听某种事件,或执行某种任务的线程
以下代码,在30分钟内,每隔30s执行onTimerFired:。这种场景通常会出如今,如我须要在应用启动以后,在必定时间内持续更新某项数据。
@autoreleasepool { NSRunLoop * runLoop = [NSRunLoop currentRunLoop]; NSTimer * udpateTimer = [NSTimer timerWithTimeInterval:30 target:self selector:@selector(onTimerFired:) userInfo:nil repeats:YES]; [runLoop addTimer:udpateTimer forMode:NSRunLoopCommonModes]; [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60*30]]; }
AFNetworking中RunLoop的建立
+ (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; // 这里主要是监听某个 port,目的是让这个 Thread 不会回收 [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [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; }