看了一下,上一篇貌似5个月前的😅。
最近公司忙着开发一个cordova的项目,本身也是边工做边找一些资料学习,都没怎么关注博客上的内容...呃,主要仍是懒癌发做吧😌。争取多写写博客,记录记录点滴,也但愿无论技能、生活仍是职业生涯上都能不断成长,共勉~
这篇是关于RunLoop的笔记的整理和一点看法。php
【本次开发环境: Xcode:7.2 iOS Simulator:iphone6 By:啊左 本文Demo下载连接:RunLoop-Demo】html
-----------------------------基本概念-----------------------------git
1、RunLoop简介github
RunLoop,跑圈。在iOS开发中,也就是运行循环。app
在应用须要的时候本身跑起来运行,在用户没有操做的时候就停下来休息。充分节省CPU资源,提升程序性能。框架
二. RunLoop的概念与做用iphone
概念:通常来说,一个线程一次只能执行一个任务,执行完成后线程就会退出。可是有时候咱们须要线程可以一直“待命”随时处理事件而不退出,这就须要一个机制来完成这样的任务。异步
这样一种机制的代码逻辑以下:函数
function loop() { initialize(); do { var message = get_next_message(); process_message(message); } while (message != quit); }
这种模型一般被称做 Event Loop。 Event Loop 在不少系统和框架里都有实现。而实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以免资源占用、在有消息到来时马上被唤醒。oop
例如一个应用放那里,不进行操做就像静止休息同样,点击按钮,就有响应,就像“随时待命”同样,这就是RunLoop的功劳。
因此RunLoop 实际上就是一个对象,这个对象管理了其须要处理的事件和消息,并提供了一个入口函数来执行RunLoop 的逻辑。
线程开始这个函数以后,便一直会处于此函数 "接受消息->等待->处理" 的循环中:(有事:作出反应; 木事:休眠省电; 再次有事:从新唤醒、处理事件。)
直到这个循环结束(好比传入 quit 的消息),最后函数返回。
做用:
RunLoop,最重要的做用,也就是用来管理线程的。能够说,没有线程,也就没有RunLoop的存在必要。
当线程的RunLoop一开启,RunLoop便开始对线程进行管理工做:在线程执行完任务后,线程便会进入休眠状态,而且不会退出,随时等待新的任务。
3、RunLoop与线程的关系
1.每条线程都有惟一的一个与之对应的RunLoop对象;
2.RunLoop在第一次获取时建立,在线程结束时销毁;只能在一个线程的内部获取其 RunLoop(主线程除外)。
3.主线程的RunLoop系统默认启动,子线程的RunLoop须要主动开启;
其实在咱们每次创建项目的时候,就已经使用上了RunLoop。
在程序的启动入口main函数中有这样一段熟悉的代码:
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
实际上UIApplicationMain 函数内部就启动了一个与主线程相关联的RunLoop。
当咱们点击运行,系统运行UIApplicationMain函数,系统进入了:主线程main的运行循环。RunLoop使得主线程一直处在运行循环中。
咱们能够作一下验证,在“Main.storyboard”中随意放置几个按钮控件,main.m文件代码修改以下:
int main(int argc, char * argv[]) { @autoreleasepool { NSLog(@"开始"); return 0; } }
点击运行,输出“开始”后,模拟器界面也是一片空白。“stop”按钮也点不下去了:;
由于当输出“开始”后,“return 0”,以后没有进入主线程运行循环,程序一启动就结束了,控件与其余程序有关的都没有执行,因此界面空白。
说明了在UIApplicationMain函数中,开启了一个和主线程相关的RunLoop,致使UIApplicationMain不会返回,一直在运行中,也就保证了程序的持续运行。
这也是为何应用可以在咱们无任何操做时休息,在咱们进行操做的时候又可以马上进行响应活动,偏偏由于应用处于RunLoop的“等待命令”的状态。
4、RunLoop对象与相关类。
对象:
从RunLoop的概念,咱们能够知道RunLoop 实际上就是一个管理着线程对象。那么,如何获取RunLoop对象呢?
[NSRunLoop currentRunLoop]; // 得到当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 得到主线程的RunLoop对象
CFRunLoopGetCurrent(); // 得到当前线程的RunLoop对象
CFRunLoopGetMain(); // 得到主线程的RunLoop对象
文档中的相关类:
CFRunLoopRef CFRunLoopSourceRef CFRunLoopTimerRef CFRunLoopModeRef CFRunLoopObserverRef
他们的关系以下图:
CFRunLoopSourceRef 输入源
是事件产生的地方,函数调用栈上Source有两个版本:Source0 和 Source1。
CFRunLoopTimerRef 定时源
基于时间的触发器,与NSTimer可混用。
包含了一个时间长度和一个回调函数。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
CFRunLoopModeRef mode类型
事实上CFRunLoopModeRef 类并无对外暴露,而若是在Xcode中查看CFRunLoopRef,能够看到CFRunLoopModeRef 类,经过 CFRunLoopRef 的接口进行了封装。
CFRunLoopModeRef有5种形式:(固然,还有一些开发中基本用不到的更多的苹果内部的 Mode:Mode介绍)
kCFRunLoopDefaultMode 默认模式,一般主线程在这个模式下运行
UITrackingRunLoopMode 界面跟踪Mode,用于追踪Scrollview触摸滑动时的状态。
kCFRunLoopCommonModes 占位符,带有Common标记的字符串,比较特殊的一个mode;
UIInitializationRunLoopMode:刚启动App时进入的第一个Mode,启动后不在使用。
GSEventReceiveRunLoop:内部Mode,接收系事件。
从关系图,咱们能够知道RunLoop一次只能指定一种Mode,且可以让不一样组的 Source/Timer/Observer互不影响,具体的实现后面会用一个项目例子来参考。
CFRunLoopObserverRef 观察者
RunLoop的观察者,可以监听RunLoop的状态改变。
每一个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能经过回调接受到这个变化,能够观察到不一样时刻的状态有如下几个:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 即将进入Loop kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠 kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒 kCFRunLoopExit = (1UL << 7), // 即将退出Loop };
-----------------------------例子-----------------------------
测试1、二的UI设计界面以下:
测试一:RunLoop的运用。
在“ViewController.m”中建立一个子线程,在线程方法中一直开启RunLoop。并在“Main.storyboard”中添加一个名为“showSource”的按钮控件,建立RunLoop事件源,使得RunLoop进入循环:
1 @interface ViewController () 2 3 @property (strong,nonatomic)NSThread *thread; //记得使用Strong属性 4 - (IBAction)showSource:(id)sender; //点击按钮,添加RunLoop事件源用。 5 6 @end 7 8 @implementation ViewController 9 10 - (void)viewDidLoad { 11 [super viewDidLoad]; 12 //建立自定义的子线程 13 self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(threadMethod) object:nil]; 14 [self.thread start]; //启动子线程 15 } 16 -(void)threadMethod 17 { 18 NSLog(@"打开子线程方法"); 19 while (1) { 20 21 //条件一:run,进入循环,若是没有source/timer就直接退出,不进入循环,后面加上source才能进入工做。 22 /*【缘由:若是线程中有须要处理的源,可是响应的事件没有到来的时候,线程就会休眠等待相应事件的发生; 23 这就是为何run loop能够作到让线程有工做的时候忙于工做,而没工做的时候处于休眠状态。】 24 */ 25 [[NSRunLoop currentRunLoop]run]; 26 27 //上面一行代码等于加了参数为1的while,因此当有source进入循环,下面这条代码的就不会运行。 28 NSLog(@"这里是threadMethod:%@", [NSThread currentThread]); 29 //若是要测试“2、addTime”按钮的话,建议注释掉上面这句代码。 30 } 31 } 32 33 #pragma mark -- 测试一:子线程Selector源的启动 34 - (IBAction)showSource:(id)sender { 35 36 //注意:在这个方法里面输出的是main主线程,由于是主线程运行的UI控件行为。 37 NSLog(@"这里是主线程:%@",[NSThread currentThread]); 38 /* 39 在没有run以前,一直处于休眠状态。因此若是要运行selector方法,还须要threadMethod中条件一不断循环的Run! 40 在咱们指定的线程中调用方法,此处至关于增长了一个带source的mode,有内容,实现了RunLoop循环运行成立的条件二。 41 */ 42 //试着在这句以前添加[[NSRunLoop currentRunLoop]run];是不能启动子线程的RunLoop,由于此处是在main主线程上。 43 [self performSelector:@selector(threadSelector) onThread:self.thread withObject:nil waitUntilDone:NO]; 44 } 45 -(void)threadSelector//【此处运行在子线程】 46 { 47 NSLog(@"打开子线程Selector源"); 48 NSLog(@"此处是threadSelector源:%@",[NSThread currentThread]); 49 }
输出结果:
2016-10-24 10:48:24.971 RunLoop演示[18111:752173] 打开子线程方法 2016-10-24 10:48:24.973 RunLoop演示[18111:752173] 这里是threadMethod:<NSThread: 0x7fc830411a70>{number = 2, name = (null)} 2016-10-24 10:48:26.256 RunLoop演示[18111:752173] 这里是threadMethod:<NSThread: 0x7fc830411a70>{number = 2, name = (null)} ........ 2016-10-24 10:48:26.260 RunLoop演示[18111:752173] 这里是threadMethod:<NSThread: 0x7fc830411a70>{number = 2, name = (null)} 2016-10-24 10:48:26.261 RunLoop演示[18111:751978] 这里是主线程:<NSThread: 0x7fc830402b30>{number = 1, name = main} 2016-10-24 10:48:26.261 RunLoop演示[18111:752173] 这里是threadMethod:<NSThread: 0x7fc830411a70>{number = 2, name = (null)} 2016-10-24 10:48:26.263 RunLoop演示[18111:752173] 打开子线程Selector源 2016-10-24 10:48:26.264 RunLoop演示[18111:752173] 此处是threadSelector源:<NSThread: 0x7fc830411a70>{number = 2, name = (null)}
分析代码:
第3行:为何子线程thread须要用到strong属性?
若是使用weak,子线程调用不了,子线程thread一建立就马上销毁了。若是咱们使用本身自定义的线程,而且重写线程的“-(void)dealloc”方法,咱们会看到其实子线程thread一建立就调用dealloc马上销毁了。
19-28行:为何要用到while?
重点:Run loop的管理并不彻底是由系统自动控制的,而是要由咱们手动显式开启。因此咱们在设计子线程代码的时候,必须符合如下条件才能进入循环:
1.RunLoop处于开启状态;(子线程由咱们手动开启)
2.正确响应输入事件;
因此第一步咱们须要使用while/for语句来驱动RunLoop,以便可以进行循环。
第37行:
经过输出线程的对象信息,咱们能够发现,此时处于UI控件按钮的事件其实属于主线程main,
(在这里有个疑问,如何把Run驱动RunLoop的代码放在此处的话,还能不能performSelector建立事件源呢?
答案是不能的,由于此时是在主线程里。也就是:Run的不是子线程:self.thread。所以也不会执行threadSelector方法)
第43行:
咱们在while中使RunLoop一直处在开启的状态,因此当建立一个Selector源时,知足条件2:RunLoop进入循环中,执行子线程的threadSelector方法,在这个RunLoop子线程处于运行循环管理中,如“while(1)”死循环通常,便不会执行后面那句输出代码,也便是中止输出 “这里是threadMethod:.........”。
(是否是相似文章开头关于main函数的测试,当进入循环后,便不会执行后面输出“结束”那段代码了。区别是主线程是默认自动开启的,而子线程的RunLoop则须要咱们手动开启。)
测试二:mode模式与定时源的同步性
在“Main.storyboard”中进行timer事件测试。
a.添加一个用于显示内容的名为“textView”的文本控件,b.再添加一个名为“addTime”的按钮控件。
@interface ViewController () //测试一 @property (strong,nonatomic)NSThread *thread; - (IBAction)showSource:(id)sender;
//测试二 @property (weak, nonatomic) IBOutlet UITextView *textView; - (IBAction)addTime:(UIButton *)sender; @end
而后在“ViewController.m”中threadSelector方法后面添加如下代码;
#pragma mark -- 2、Time测试 - (IBAction)addTime:(UIButton *)sender { NSTimer *timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(showTimer) userInfo:nil repeats:YES];
//添加timer到RunLoop [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode]; } -(void)showTimer //【在主线程】 { NSLog(@"调用time的线程:%@",[NSThread currentThread]); [self showText:@"-------time-------"]; } #pragma mark --在文本控件textView后面增长str字符串 -(void)showText:(NSString *)str //注意:由于UI控件须要在主线程里面,尝试一下,若是是在子线程threadMethod方法执行此段代码则运行报错。 { NSString *text = self.textView.text; self.textView.text = [text stringByAppendingString:str]; }
关于mode模式:
操做:当点击addTime按钮后,textView控件上不断显示“-------time-------”,可是当咱们拖拽textView进度条上下移动时,会发现"-(void)showTime:"不会执行,textView控件上的内容再也不增长“-------time-------”,就像“卡住了,死机了”同样。当咱们中止对textView进行拖拽后,控件上的内容又不断添加更新了。
解决方案:修改mode类型:把默认模式NSDefaultRunLoopMode改成占位符NSRunLoopCommonModes;
发现若是修改为这样,那么即便咱们对textView进行拖拽,内容会一直增长“-------time-------”,不再会因为拖拽而被牵制住了。
缘由:每次RunLoop只能支持一种mode。当咱们点击addtime按钮后,定时源(timer)加入到RunLoop中,而当滑动textView时,RunLoop自动切换成UITrackingRunLoopMode模式,定时器就中止了响应。
而NSRunLoopCommonModes等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode两种模式的结合
因此当咱们在带有 “Common ”标记的NSRunLoopCommonModes模式下添加定时源(timer)后。即便咱们对textView进行滚动操做,也不会影响到内容的显示了。
另外提一下,还有另外一种添加time的方法:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(showTimer) userInfo:nil repeats:YES];
//使用scheduledTimerWithTimeInterval方法,会自动添加到RunLoop,因此能够不写如下代码,只是会默认为NSDefaultRunLoopMode模式 [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
关于同步:
当咱们观察控制台的输出,能够发现,其实调用 "-(void)showTimer" 输出的是在主线程mian中。
这是由于输入源使用传递异步事件,且一般消息来自于其余线程或程序。
而定时源是在以同步方式传递信息的。
-----------------------------其余补充-----------------------------
1.RunLoop输入源的结构图以下:
RunLoop接收输入事件来自两种不一样的来源:输入源(input source)和定时源(timer source)。
输入源:传递异步事件,一般消息来自于其余线程或程序。
输入源有3种类型:
在测试一中,当咱们点击按钮后,执行UI按钮控件的事件,此时“performSelector”一个Selector输入源,因此,系统执行Selector方法。
2.RunLoop的内部流程的逻辑以下:
(备注:左边黄色的地方,“source0 (port) ”改成"source1 (port)")
因此在测试一中,处于while一直进行着的语句:
[[NSRunLoop currentRunLoop]run];
每次的Run都表明着:进行一次消息轮询,若是没有任务须要处理的消息源,则直接返回;
---------------
本文主要阐述基本概念与应用,若是有兴趣的童鞋能够参考:
2.ibireme的文章,关于RunLoop背后的底层原理的详解:
【http://blog.ibireme.com/2015/05/18/runloop/】
三、以及这篇关于输入源定时源的详解介绍: