ObjC RunLoop简析
栏目: Objective-C · 发布时间: 6年前
内容简介:当我们创建一个terminal项目的时候,此时的main函数中并没有一个RunLoop。所以程序运行完main函数之后就退出了。而一个iOS的application程序,默认在主线程开启了一个RunLoop,这样一个App就可以处理一些计时器事件,滑动事件等,不会马上退出。在iOS项目中每一条线程都对应着一个RunLoop对象,RunLoop存放在一个以线程作为key的散列表中。
当我们创建一个terminal项目的时候,此时的main函数中并没有一个RunLoop。所以程序运行完main函数之后就退出了。
而一个iOS的application程序,默认在主线程开启了一个RunLoop,这样一个App就可以处理一些计时器事件,滑动事件等,不会马上退出。
在iOS项目中每一条线程都对应着一个RunLoop对象,RunLoop存放在一个以线程作为key的散列表中。
主线程在创建的时候默认开启RunLoop,而其他子线程默认不开启,但是会在第一次获取RunLoop( [NSRunLoop currentRunLoop]或者CFRunLoopGetCurrent()
)的时候创建。
一般情况下RunLoop的生命周期跟随线程,线程结束的时候RunLoop也会被销毁。
iOS中提供了一套Foundation框架的NSRunLoop api和一套 基于Core Foundation的CFRunLoopRef api 来使用RunLoop。其中NSRunLoop是基于CFRunLoopRef做了一层OC的封装。
RunLoop 结构
在CFRunLoop的源码中RunLoop的基本结构如下:
CFRunLoopRef是一个 __CFRunLoop
的结构体,结构体中存放了许多mode相关的成员。
其中 _currentMode
是 CFRunLoopModeRef
类型的,它是一个 __CFRunLoopMode
类型的结构体指针。RunLoop通过它来表征RunLoop的运行状态。
一个RunLoop中包含有许多Mode。 _commonModes
是一个可变的集合,集合中存放了许多mode。RunLoop在运行的时候只能选择一个Mode作为当前RunLoop执行的状态,也就是 _currentMode
。
mode是 CFRunLoopMode
类型的。而 CFRunLoopMode
是通过typedf __CFRunLoopMode
得到的。 __CFRunLoopMode
中存放了处理触摸事件的 source0
、系统时间捕捉的 source1
、处理计时器的 timers
、监听RunLoop状态的 observer
等。
另外,如果RunLoop需要切换运行状态的时候必须先退出当前的Mode,才能进入新的Mode。如果当前Mode中所有的 source
、 timer
、 observer
的时候RunLoop就会立刻退出。
常见的RunLoopMode有默认的mode kCFRunLoopDefaultMode
、跟踪界面(比如:保证滑动不受其他mode影响)的 UITrackingRunLoopMode
。另外在api中还有一个 kCFRunLoopCommonModes
但是这并不是一个真正的mode,它不存在于 _commonModes
中,它只是一个标记。
RunLoop 的监听器会监听RunLoop的一些状态:
/* Run Loop Observer Activities */ typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 即将进入runloop kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理计时器 kCFRunLoopBeforeSources = (1UL << 2), // 即将处理source kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠 kCFRunLoopAfterWaiting = (1UL << 6), // 即将从休眠中唤醒 kCFRunLoopExit = (1UL << 7), // 即将退出RunLoop kCFRunLoopAllActivities = 0x0FFFFFFFU // 全部状态 }; 复制代码
探究RunLoop的执行流程
我们可以通过Xcode自带的lldb 通过 bt
命令查看函数调用栈找到RunLoop的入口函数
在 CFRunLoop.c
文件中找到该函数,我们发现它通过 __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry)
监听进入RunLoop。接着有一个 __CFRunLoopRun
的函数调用,该函数中封装了RunLoop处理事件的逻辑。
我们只关注 __CFRunLoopRun
主要代码:我们发现该函数中存在着一个 do-while()
循环,当 retVal==0
的时候循环持续进行,当 retVal != 0
的时候,回返回给函数 CFRunLoopRunSpecific
,它调用 __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
退出RunLoop。
在 __CFRunLoopRun
中主要的流程都在下图中进行了描述。总结来说:
- 首先会通知监听者Observers:即将处理Timers
- 通知监听者Observers:即将处理Sources
- 处理blocks
- 处理source0,如果处理完了会再次处理blocks
- 如果存在source1,则跳到handle_msg处理,如果没有则通知监听器即将进入休眠
- 休眠时期等待消息来唤醒当前线程
- 如果有消息唤醒则进入handle_msg处理计时器,gcd,source1这些信息
- 再次处理blocks
- 获取返回值retVal
- 进入
do-while()
,如果retVal == 0
则循环持续进行。否则返回给CFRunLoopRunSpecific
函数,退出RunLoop
RunLoop的应用
Timer
当我们使用 + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
创建一个计时器,并且页面存在scrollView的时候。滑动scrollView计时器就会停止运行。这是因为一开始runloop存在于 NSDefaultRunLoopMode
,当滑动事件响应的时候runloop会进入 UITrackingRunLoopMode
模式处理滑动事件,所有timer就会失去处理。
我们可以使用 + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
创建timer,然后将它放在一个 NSRunLoopCommonModes
标记的模式下进行工作,timer是可以t在_commonModes数组中存放的模式下工作的。这样就解决了滑动事件和timer计时器事件冲突的问题。
线程保活
当我们创建一条线程的时候,这条线程并没有一个RunLoop。当我们第一次获取RunLoop的时候这条线程中才会创建RunLoop。
所以创建一条一直存在的线程,我们需要在线程中加入一个不会被回收的RunLoop,也就是让 do-while()
一直存在,也就是RunLoop一直有事情在处理,而 retVal
不会为不是0的其他值。
实例代码:
#import "SoCPermanentThread.h" @interface SoCPermanentThread () @property (nonatomic, strong) NSThread *thread; @end @implementation SoCPermanentThread - (instancetype)init { if (self = [super init]) { self.thread = [[NSThread alloc] initWithBlock:^{ CFRunLoopSourceContext context = {0}; CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); CFRelease(source); CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false); }]; [self.thread start]; } return self; } - (void)executeTask:(SoCPermenantThreadTask)task { if (!_thread || !task) return; [self performSelector:@selector(__task:) onThread:_thread withObject:task waitUntilDone:NO]; } - (void)stop { if (!_thread) return; [self performSelector:@selector(__stop) onThread:_thread withObject:nil waitUntilDone:YES]; } - (void)__task:(SoCPermenantThreadTask)task { task(); } - (void)__stop { CFRunLoopStop(CFRunLoopGetCurrent()); _thread = nil; } - (void)dealloc { [self stop]; } @end 复制代码
上述代码使用 Core Foundation
实现的线程保活,其中重要的就是首先往RunLoop中添加Source保证RunLoop有事情可以做,另外就是 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
这个方法的最后一个参数BOOL参数 returnAfterSourceHandled
,其值为 flase
代表执行完函数(处理完source)不会返回,而 true
则相反,表示执行完函数(处理完source)会立即返回。
总结
本篇主要以 Core Foundation
api 为基础( Core Foundation
开源)简述了RunLoop的基本概念,和调用流程,由于 NSRunLoop
是基于 CFRunLoop
做的OC封装,其原理和流程都是一样的。另外介绍了两个使用RunLoop的案例。
以上所述就是小编给大家介绍的《ObjC RunLoop简析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Everything Store
Brad Stone / Little, Brown and Company / 2013-10-22 / USD 28.00
The definitive story of Amazon.com, one of the most successful companies in the world, and of its driven, brilliant founder, Jeff Bezos. Amazon.com started off delivering books through the mail. Bu......一起来看看 《The Everything Store》 这本书的介绍吧!