在iOS开发中,会经常用到RunLoop,面试的时候更是必问的东西,RunLoop也是iOS中非常重要的东西,趁着假期有空,研究一下,做篇笔记.
RunLoop是什么
顾名思义,run+loop,其实就是运行循环.
RunLoop做什么用
看两段代码
命令式执行
<pre>
int main(int argc, char * argv[]) {
NSLog(@"Hello,RunLoop");
return 0;
}
</pre>Event驱动
<pre>int main(int argc, char * argv[]) {
while (AppISRunning) {
id whoWeakMe = sleepForWeakingUp();
id event = GetEvent(whoWeakMe);
HandleEvent(event);
}
return 0;
}</pre>
RunLoop其实就是Event驱动的方式,有事件需要进行处理的时候处理事件,处理完毕就进行sleep.iOS中使用RunLoop能带来以下好处
- 使程序一直运行并接受用户输入
- 决定程序在何时应该处理哪些Event
- 调用解耦
- 节省CPU时间
RunLoop in cocoa
CFRunLoop是基于C语言的开源的核心文件,NSRunLoop是对CFRunLoop的OC封装,所以如果想深入的研究,可以直接看CFRunLoop的内容.iOS中用到RunLoop的地方比较多,下面图中的东西基本都涉及到.
主线程几乎所有的函数都是以上六个之一的函数调起,可以在xcode的调用栈中看到
RunLoop 机制
- CFRunLoop 与Thread是一一对应的
- CFRunLoopTimer
CFRunLoopTimer的封装应该是大家最为常用也是最为熟悉的了,比如下面一些常见的方法:
<pre>
+(NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
-(void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSString *> *)modes;
</pre> - CFRunLoopSource
source是RunLoop的数据源抽象类,RunLoop定义了两个版本source:
1.source0:处理APP内部事件,APP自己负责管理(触发),如UIEvent,CFSocket.
2.source1:由RunLoop和内核管理,mach port驱动,如CFMachPort,CFMessagePort. - CFRunLoopObserver
主要用来向外部报告RunLoop当前状态的更改,RunLoop的状态主要有以下几种:
<pre>
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
};
</pre> - CFRunLoopMode:分为四种mode:
- NSDefaultRunLoopMode:默认状态,空闲状态
- UITrackingRunLoopMode:滑动UIScrollview时
- UIInitializationRunLoopMode:私有,APP启动时
- NSRunLoopCommonModes:Mode集合(可以理解为NSDefaultRunLoopMode和UITrackingRunLoopMode的集合)
需要注意的是:
1.RunLoop在同一时间内只能且必须在一种特定的mode下运行
2.更换mode时,需要停止当前的loop,然后重启新loop
3.mode是iOS APP 滑动顺畅的关键
4.可以定制自己的mode(当然,一般用不上)
Runloop的挂起和唤醒
- 空闲时刻暂行程序时挂起
- 指定用于唤醒的mach_port端口
- 调用mach_msg监听唤醒端口,被唤醒前,系统内核将这个线程挂起,停留在mach_msg_trap状态.下面是RunLoop迭代执行顺序的伪代码
<pre>
[SetupThisRunLoopRunTimeoutTimer(); // by GCD timer
do {
__CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(kCFRunLoopBeforeSources);
__CFRunLoopDoBlocks();
__CFRunLoopDoSource0();
CheckIfExistMessagesInMainDispatchQueue(); // GCD
__CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);
var wakeUpPort = SleepAndWaitForWakingUpPorts();
// mach_msg_trap
// Zzz...
// Received mach_msg, wake up
__CFRunLoopDoObservers(kCFRunLoopAfterWaiting);
// Handle msgs
if (wakeUpPort == timerPort) {
__CFRunLoopDoTimers();
} else if (wakeUpPort == mainDispatchQueuePort) {
// GCD
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE()
} else {
__CFRunLoopDoSource1();
}
__CFRunLoopDoBlocks();
} while (!stop && !timeout);]
</pre>
RunLoop应用
-
RunLoopobserver 与 AutoreleasePool
AutoreleasePool什么时候释放,这是个面试经常问的问题.我们可以看下xcode的调用栈.酒杯图标就是UIKit做的处理.可以看出来,UIKit通过RunLoopObserver在RunLoop两次sleep中间对AutoreleasePool进行pop和push,将这次loop中产生的Autorelease对象释放.
UITrackingRunLoopMode与Timer
创建Timer有两种方式
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(doSomeThing) userInfo:nil repeats:YES]
-
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(doSomeThing) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
第1中方式创建的time将被添加到NSDefaultRunLoopMode中,UIScrollview滑动是将不执行timer.第2中方式创建的time被添加到NSRunLoopCommonModes中,UIScrollview滑动时timer也能执行.
RunLoop与dispatch_get_main_queue()
GCD中的dispatch到main queue中的block将被分发到main RunLoop中执行.AFNetworking中RunLoop的创建
这是一种常驻服务线程创建的好方法
<pre>
+(void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[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;
}
</pre>
ps:这是第一次用markdown写博客,感觉写的好乱,代码段
好像框不住....就先这样吧,有空再来修改格式...