介绍RunLoop一直是一个神秘的领域。很多工作了2.3年的开发者都不能准确描述它的功能,称其神秘莫测。其实RunLoop并没有大家想象的那么神秘,所以并不那么好理解,本文就带大家一起来分析一下“神秘的RunLoop”什么是RunLoop顾名思义,runningloop的基本作用是让程序持续运行(比如作为主运行循环)并处理App中的各种事件(如触摸事件、定时器事件、选择器事件)以节省CPU资源并提高程序性能:该做的事做,该休息的时候休息。没有RunLoop,有一个RunLoop主运行循环,而代码第14行的UIApplicationMain函数在里面启动了一个RunLoop,所以UIApplicationMain函数一直没有返回,保持程序一直在运行。默认启动的RunLoop是一个与主线程关联的RunLoop对象。iOS中有两套API可以访问和使用RunLoopF??oundationNSRunLoopCoreFoundationCFRunLoopRef。NSRunLoop和CFRunLoopRef都代表RunLoop对象。NSRunLoop基于CFRunLoopRef。一层OC封装,所以要了解RunLoop的内部结构,需要研究CFRunLoopRef级别(CoreFoundation级别)的APIRunLoop资料苹果官方文档https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.htmlCFRunLoopRef是开源的http://opensource.apple.com/source/CF/CF-1151.16/RunLoopandThread每个线程都有一个唯一的RunLoop对象与之对应。主线程的RunLoop已经自动创建,子线程的RunLoop需要主动创建。RunLoop在第一次获取时创建,并在线程结束时销毁。获取的RunLoop对象Foundation[NSRunLoopcurrentRunLoop];//获取当前线程的RunLoop对象[NSRunLoopmainRunLoop];//获取主线程的RunLoop对象CoreFoundationCFRunLoopGetCurrent();//获取当前线程的RunLoop对象主线程RunLoop相关的RunLoop对象CoreFoundation中关于RunLoop的五个类:CFRunLoopRefCFRunLoopModeRefCFRunLoopSourceRefCFRunLoopTimerRefCFRunLoopObserverRef注意:如果RunLoop没有这些东西,会直接退出CFRunLoopModeRefCFRunLoopModeRef表示RunLoop的运行模式。一个RunLoop包含若干个Mode,每个Mode包含若干个Source/Timer/Observer,每个RunLoop在启动时,只能指定其中一个Modes。此模式称为CurrentMode。如果需要切换Mode,只能退出Loop,然后重新指定一个Mode进入。这主要是为了将不同组的Source/Timer/Observer分开,使其互不影响。相关系统默认注册了5个Modes:(前两个和最后一个常用)kCFRunLoopDefaultMode:App的默认Mode,通常主线程运行在这个Mode。UITrackingRunLoopMode:界面TrackingMode,用于ScrollViewTrack触摸和滑动,保证界面滑动时不被其他模式影响。UIInitializationRunLoopMode:App刚启动时进入的第一个Mode,GSEventReceiveRunLoopMode:接受系统事件的内部Mode,一般不用kCFRunLoopCommonModes:这是一个占位符Mode,不是真正的ModeCFRunLoopSourceRefCFRunLoopSourceRef是一个事件源(输入源)根据官方文件Port-BasedSources的分类(基于端口,与其他线程交互,内核发布的消息)CustomInputSources(自定义)CocoaPerformSelectorSources(performSelector...method)根据函数调用栈的分类Source0:Non-Port-basedSource1:Port-basedSource0:event事件,只包含一个回调,需要先调用CFRunLoopSourceSignal(source),将这个Source标记为pending,然后手动调用CFRunLoopWakeUp(runloop)唤醒RunLoopSource1:包含一个mach_port和一个回调,使用通过内核和其他线程互相发送消息可以主动唤醒RunLoop线程。函数调用堆栈函数调用堆栈CFRunLoopTimerRefCFRunLoopTimerRef是一个基于时间的触发器。基本上就是NSTimer(CADisplayLink也加入了RunLoop),受RunLoopMode的影响。GCD计时器不受RunLoop模式的影响。CFRunLoopObserverRefCFRunLoopObserverRef是一个观察者。可以监听RunLoop状态变化的时间点如下:-(void)observer{//创建观察者CFRunLoopObserverRefobserver=CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(),kCFRunLoopAllActivities,YES,0,^(CFRunLoopObserverRefobserver,CFRunLoopActivityactivity){NSLog(@"----监听RunLoop状态变化---%zd",activity);});//添加观察者:监听RunLoop状态CFRunLoopAddObserver(CFRunLoopGetCurrent(),observer,kCFRunLoopDefaultMode);//释放ObserverCFRelease(observer);}特别注意/*CF的内存管理(CoreFoundation)1.凡是用Create、Copy、Retain等字眼的函数创建的对象,都需要在***时释放一次,例如CFRunLoopObserverCreate2.release函数:CFRelease(对象);*/RunLoop处理逻辑-官方版逻辑-网友编辑版网友版注:进入RunLoop前会判断mode是否为空,如果为空则直接退出RunLoop应用NSTimerImageView显示PerformSelector常驻线程自动释放池1。NSTimer(最常见的RunLoop使用)-(void)timer{NSTimer*timer=[NSTimertimerWithTimeInterval:2.0target:selfselector:@selector(run)userInfo:nilrepeats:YES];//定时器只在NSDefaultRunLoopMode下运行,一旦RunLoop进入其他模式,该定时器将不起作用//[[NSRunLoopcurrentRunLoop]addTimer:timerforMode:NSDefaultRunLoopMode];//定时器只运行在UITrackingRunLoopMode,一旦RunLoop进入其他模式,该定时器将不起作用//[[NSRunLoopcurrentRunLoop]addTimer:timerforMode:UITrackingRunLoopMode];//定时器将运行在标记为commonmodes的模式中//标记为commonmodes的模式:UITrackingRunLoopMode和NSDefaultRunLoopMode是兼容的[[NSRunLoopcurrentRunLoop]addTimer:timerforMode:NSRunLoopCommonModes];}-(void)timer2{//scheduledTimer返回的定时器已经被调用,已经自动添加到当前InrunLoop,并且是NSDefaultRunLoopModeNSTimer*timer=[NSTimerscheduledTimerWithTimeInterval:2.0target:selfselector:@selector(run)userInfo:nilrepeats:YES];//修改模式[[NSRunLoopcurrentRunLoop]addTimer:timerforMode:NSRunLoopCommonModes];}当场景还原并拖动时mode由NSDefaultRunLoopMode改为UITrackingRunLoopMode,如下图:NSTimer不再响应图片并停止轮播NSDefaultRunLoopMode模式NSRunLoopCommonModes模式这两种模式此时都可以运行如下图:NSTimer在两种模式下都可以正常运行2.ImageView要求:当用户拖动时(UI交互时)不显示图片,拖动完成后显示图片@selector(setImage:)withObject:[UIImageimageNamed:@"placeholder"]afterDelay:3.0inModes:@[NSDefaultRunLoopMode]];3.PerformSelectorinModes:设置运行模式4.Permanent线程(重要)应用场景:经常在后台消费临时操作,如:监控联网状态,扫描沙箱等。不希望线程处理完就销毁event,保持驻留状态***(推荐)打开-(void)run{//addPort:添加端口(即source)forMode:设置模式[[NSRunLoopcurrentRunLoop]addPort:[NSPortport]forMode:NSDefaultRunLoopMode];//StartRunLoop[[NSRunLoopcurrentRunLoop]run];/*//另外两种启动方式[NSDatedistantFuture]:遥远的未来同上run是一个意思[[NSRunLoopcurrentRunLoop]runMode:NSDefaultRunLoopModebeforeDate:[NSDatedistantFuture]];不要设置模式[[NSRunLoopcurrentRunLoop]runUntilDate:[NSDatedistantFuture]];*/}exit-退出当前线程[NSThreadexit];第二种(妙法)优点:退出RunLoop比较方便——定义一个flagwhile(flag){...}-(void)run{while(1){[[NSRunLoopcurrentRunLoop]run];}}5.释放,在处理事件之前创建一个释放池,中间创建的对象会被放入释放池中。特别注意:在开始RunLoop之前,推荐使用@autoreleasepool{...}来包裹意思:创建一个大的释放池,释放{}期间创建的临时对象,一般好的框架的作者都会这样做-(void)execute{@autoreleasepool{NSTimer*timer=[NSTimertimerWithTimeInterval:2.0target:selfselector:@selector(run)userInfo:nilrepeats:YES];[[NSRunLoopcurrentRunLoop]addTimer:timerforMode:NSDefaultRunLoopMode];[[NSRunLoopcurrentRunLoop]run];}}当用户UI交互不需要事件处理,我们可以把需要做的操作放在NSDefaultRunLoopMode中补充:GCD定时器的一般NSTimer定时器会因为RunLoop不准时。上面说了GCD不受RunLoop的影响,下面简单说一下它的使用/**timer(这里不需要带*,因为dispatch_source_t是一个类,里面已经包含了*)*/@property(nonatomic,strong)dispatch_source_ttimer;intcount=0;-(void)touchesBegan:(NSSet*)toucheswithEvent:(UIEvent*)event{//getqueue)self.timer=dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0,0,queue);//设置定时器的各种属性(什么时候开始任务,多久执行一次)//GCD时间参数,一般为纳秒NSEC_PER_SEC(1秒==10纳秒的9次方)//什么时候开始执行第一个任务//dispatch_time(DISPATCH_TIME_NOW,3.0*NSEC_PER_SEC)比当前时间晚3秒dispatch_time_tstart=dispatch_time(DISPATCH_TIME_NOW,(int64_t)(1.0*NSEC_PER_SEC));uint64_tinterval=(uint64_t)(1.0*NSEC_PER_SEC);dispatch_source_settimer,(start.,0);//设置回调dispatch_source_set_event_handler(self.timer,^{NSLog(@"------------%@",[NSThreadcurrentThread]);count++;//if(count==4){////取消定时器//dispatch_cancel(self.timer);//self.timer=nil;//}});//启动定时器dispatch_resume(self.timer);}RunLoop面试题经常会有一个喜欢装B的面试官,面试的时候喜欢问RunLoop。事实上,他真的知道吗?也许他自己也不明白。总结一下,什么是RunLoop?字面意思:runningloop,runningcircle其实就是里面一个do-while循环,各种任务(比如Source,Timer,Observer)都在这个循环里面不断的处理。一个线程对应一个RunLoop,主线程默认已经启动了RunLoop,需要手动启动子线程的RunLoop(调用run方法)。RunLoop只能选择一个Mode来启动。如果当前Mode中没有Soure、Timer、Observer,则直接退出RunLoop。开发中如何使用RunLoop?什么应用场景?启动一个常驻线程(保持一个子线程不死,等待其他线程的消息,处理其他事件)在子线程中启动一个定时器,并在子线程中进行一些长期的监控,以控制定时器的执行inaspecificmode允许某些事件(行为、任务)在特定模式下执行。可以添加Observer来监控RunLoop的状态,比如监控点击事件的处理(在所有的点击事件中在这篇文章之前做点什么)***之前发表的文章不是很完整,花了两天时间重新整理了一下。如有不足,欢迎大家指出,我会第一时间更新。
