RunLoop
从字面意思看:
1.运行循环
2.跑圈
基本作用(重要)
1.保持程序的持续运行。
2.处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)。
3.节省CPU资源,提高程序性能:该做事时做事,该休息时休息。
......
- 一、如果
没有
RunLoop
没有RunLoop的情况下:第3行后程序就结束了。
- 二、如果
有了
RunLoop
有RunLoop的情况下:
由于main函数里面启动了个RunLoop,所以程序并不会马上退出,保持持续运行状态。
// 用DefaultMode启动
// RunLoop的主函数,是一个死循环
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
//CFRunLoopRunSpecific具体处理runloop的运行情况
//CFRunLoopGetCurrent() 当前runloop对象
//kCFRunLoopDefaultMode runloop的运行模式的名称
//1.0e10 runloop默认的运行时间,即超时为10的九次方
//returnAfterSourceHandled 回调处理
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
//如果runloop没有停止且没有结束则继续循环
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
- 三、
main
函数中的RunLoop
第14行代码的
UIApplicationMain
函数内部就启动了一个RunLoop
。
1.所以UIApplicationMain
函数一直没有返回,保持了程序的持续运行。
2.这个默认启动的RunLoop
是跟主线程
相关联的。
RunLoop对象
- iOS中有2套API来访问和使用RunLoop:
1.Foundation:
NSRunLoop
。
2.Core Foundation:CFRunLoopRef
。
1.
NSRunLoop
和CFRunLoopRef
都代表着RunLoop
对象。
2.NSRunLoop
是基于CFRunLoopRef
的一层OC
包装,所以要了解RunLoop
内部结构,需要多研究CFRunLoopRef
层面的API(Core Foundation
层面)。
RunLoop与线程
1.每条
线程
都有唯一
的一个与之对应的RunLoop
对象(字典:线程作为key, RunLoop作为value)
。
2.主线程
的RunLoop
已经自动创建好了,子线程的RunLoop需要主动创建
。
3RunLoop
在第一次
获取时创建,在线程结束
时销毁。
- 主线程对应的runloop默认已经创建并且开启了,而子线程对应的runloop需要手动创建并开启
//获得runloop(创建runloop)
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
// 创建字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 创建主线程对应的runloop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 使用字典保存主线程-主线程对应的runloop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 从字典中获取子线程的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
// 如果子线程的runloop不存在,那么就为该线程创建一个对应的runloop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 把当前子线程和对应的runloop保存到字典中
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//OC语言的API
//01 获得主线程对应的runloop对象 主运行循环
NSRunLoop *mainRunloop = [NSRunLoop mainRunLoop];
//NSLog(@"%@",mainRunloop);
//02 获得当前的runloop对象
NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];
NSLog(@"%p---%p",currentRunloop,mainRunloop);
//C语言的API
//01 主运行循环
CFRunLoopRef mainRunloopRef = CFRunLoopGetMain();
//02 当前的运行循环
CFRunLoopRef currentRunloopRef = CFRunLoopGetCurrent();
NSLog(@"%p-%p",mainRunloopRef,currentRunloopRef);
//转换
NSLog(@"%p--%p",mainRunloop.getCFRunLoop,mainRunloopRef);
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
}
-(void)run{
NSLog(@"run---%@",[NSThread currentThread]);
//子线程调用
//NSLog(@"%@",[NSRunLoop currentRunLoop]);
//runloop和线程的关系
//(1) 一一的对应的关系(字典)
//(2) 主线程对应的runloop默认已经创建并且开启了,而子线程对应的runloop需要手动创建并开启
//(3) 线程销毁,那么runloop也将销毁
//获得子线程对应的runloop |currentRunLoop该方法本身是懒加载的,如果是第一调用那么会创建当前线程对应的runloop并保存,以后调用则直接获取
NSRunLoop *newThreadRunloop = [NSRunLoop currentRunLoop];
NSLog(@"%@",newThreadRunloop);
[newThreadRunloop run]; //开启runloop (该runloop开启后马上退出了)
因为在该子线程中的运行模式是nil,该runloop开启后马上退出了.
}
@end
注意:
runloop
在启动时,要选择一种运行模式,之后判断这个运行模式是否为nil
(判断运行模式中的source、timer是否为nil,如果为nil,则该运行模式就为nil
),若所选的运行模式为nil
,这个runloop
就会马上退出;若所选的运行模式中的source\timer
不为nil
,runloop
就会起来一直运行(死循环)。
- 获得RunLoop对象
Foundation:
1.[NSRunLoop currentRunLoop];
// 获得当前线程的RunLoop对象
2.[NSRunLoop mainRunLoop];
// 获得主线程的RunLoop对象
Core Foundation
1.CFRunLoopGetCurrent();
// 获得当前线程的RunLoop对象
2.CFRunLoopGetMain();
// 获得主线程的RunLoop对象
- RunLoop相关类
(核心)
Core Foundation
中关于RunLoop
的5
个类
1.CFRunLoopRef
(RunLoop本身)
2.CFRunLoopModeRef
(RunLoop运行模式)
3.CFRunLoopSourceRef
(输入源事件模式)
4.CFRunLoopTimerRef
(定时器时间)
5.CFRunLoopObserverRef
(观察者)
图很重要
- CFRunLoopModeRef
一、
CFRunLoopModeRef
代表RunLoop
的运行模式
。
1.一个RunLoop
包含若干个 Mode(运行模式)
,每个Mode(运行模式)
又包含若干个Source/Timer/Observer
。
2.每次RunLoop
启动时,只能指定其中一个 Mode
,这个Mode
被称作CurrentMode
。
3.如果需要切换Mode
,只能退出Loop
,再重新指定一个Mode进入
(该过程是自动进行)。
4.这样做主要是为了分隔开不同组
的Source/Timer/Observer
,让其互不影响
。
二、系统默认注册了
5
个Mode
:
1.kCFRunLoopDefaultMode
:App
的默认Mode
,通常主线程
是在这个Mode
下运行。
2.UITrackingRunLoopMode
:界面跟踪 Mode
,用于ScrollView
追踪触摸滑动
,保证界面滑动
时不受其他Mode
影响。
3.UIInitializationRunLoopMode
: 在刚启动 App
时,进入的第一个 Mode
,启动完成后
就不再使用
。
4.GSEventReceiveRunLoopMode
:接受系统事件的内部 Mode
,通常用不到。
5.kCFRunLoopCommonModes
: 这是一个占位用的Mode
,不是一种真正的Mode。
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// [self timer1];
// [self timer2];
//子线程中开启定时器
[self performSelectorInBackground:@selector(timer2) withObject:nil];
}
-(void)timer1
{
//01 创建定时器对象
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//02 添加到runloop中
//Mode :runloop的运行模式(5-默认|界面跟踪|占位)
//把定时器对象添加到runloop中,并指定运行模式为默认:(只有当运行模式为NSDefaultRunLoopMode的时候,定时器才会工作)
//[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//当滚动textView的时候,主运行循环会切换运行模式(默认->界面追踪运行模式)
//把定时器对象添加到runloop中,并指定运行模式为界面跟踪:(只有当运行模式为NUITrackingRunLoopMode的时候,定时器才会工作)
//[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
//要求不管有没有在滚动控件,定时器都能够正常工作
//[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
//把定时器对象添加到runloop中,并指定运行模式为commonModes:
//只有当运行模式为被标记为commonModes的运行模式的时候,定时器才会工作
//被标记为commonModes运行模式:UITrackingRunLoopMode | kCFRunLoopDefaultMode
/*
common modes = <CFBasicHash 0x7fa4c9d00d70 [0x10a3f17b0]>{type = mutable set, count = 2,
entries =>
0 : <CFString 0x10b2ed270 [0x10a3f17b0]>{contents = "UITrackingRunLoopMode"}
2 : <CFString 0x10a411b60 [0x10a3f17b0]>{contents = "kCFRunLoopDefaultMode"}
}*/
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
-(void)timer2
{
NSLog(@"timer++++++%@",[NSThread currentThread]);
//定时器工作
//该方法内部会自动将创建的定时器对象添加到当前的runloop,并且指定运行模式为默认
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//手动创建子线程对应的runloop
[[NSRunLoop currentRunLoop] run];
}
-(void)run
{
NSLog(@"run----%@",[NSRunLoop currentRunLoop].currentMode);
}
- CFRunLoopSourceRef是事件源(输入源)
1、以前的分法:
Port-Based Sources
Custom Input Sources
Cocoa Perform Selector Sources
2、现在的分法
Source0
:非基于Port
的(用户手动触发的:如按钮的点击事件)。
Source1
:基于Port
的(系统触发的事件)。
- CFRunLoopTimerRef(定时器)
1.
CFRunLoopTimerRef
是基于时间的触发器。
2.基本上说的就是NSTimer
。
- CFRunLoopObserverRef(观察者)
1.
CFRunLoopObserverRef
是观察者,能够监听RunLoop
的状态
改变。
2.可以监听的时间点有以下几个:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
//即将进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1),
//即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2),
//即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5)
,//即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6)
,//刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7)
//即将退出RunLoop
};
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//01 创建观察者对象
/**
* 参数说明
* 第一个参数:分配存储空间(默认:CFAllocatorGetDefault())
* 第二个参数:要监听的状态
* 第三个参数:是否要持续监听 YES
* 第四个参数:默认0
* 第五个参数:block回调,当RunLoop状态改变的时候会调用
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
/*
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
*/
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"runloop进入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理timer事件");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理source事件");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"被唤醒");
break;
case kCFRunLoopExit:
NSLog(@"runloop退出");
break;
default:
break;
}
});
//02 监听当前runloop的运行状态
/**
* 参数说明
* 第一个参数:RunLoop对象
* 第二个参数:监听者
* 第三个参数:RunLoop在哪种运行模式下的状态
* NSDefaultRunLoopMode == kCFRunLoopDefaultMode
* NSRunLoopCommonModes == kCFRunLoopCommonModes
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
[NSTimer scheduledTimerWithTimeInterval:4.0 target:self selector:@selector(timerRun) userInfo:nil repeats:YES];
}
-(void)timerRun
{
NSLog(@"处理收到的事件--timer--RUN");
}
@end
-
网友分析RunLoop处理逻辑图
-
官方RunLoop处理逻辑过程
代码模拟RunLoop
#import <Foundation/Foundation.h>
void msg(int n)
{
NSLog(@"runloop被唤醒");
NSLog(@"<<<<<<< 处理收到的任务 >>>>>>>>");
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
do {
NSLog(@"有事件需要我处理吗?");
NSLog(@"没有的话我就休息了!");
NSLog(@"Runloop进入休眠.....zzz");
int number = 0;
scanf("%d",&number);
msg(number);
} while (1);
}
return 0;
}
- RunLoop在iOS开发中的应用
1、该方法会受到RunLoop的影响。
[ performSelector:<#(nonnull SEL)#> withObject:<#(nullable id)#> afterDelay:<#(NSTimeInterval)#> ];
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//该方法内部会自动把事件添加到当前的RunLoop中,并且指定运行模式为默认模式(kCFRunLoopDefaultMode),在滚动TextView的时候没有切换到滚动模式(UITrackingRunLoopMode)
// [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0];
// [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSRunLoopCommonModes]];
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode,UITrackingRunLoopMode]];
}
@end
2、线程保活(开启一条常驻的子线程)
#import "ViewController.h"
@interface ViewController ()
@property (strong, nonatomic)NSThread *thread;
@end
@implementation ViewController
- (IBAction)createThreadBtnClick:(id)sender {
//01创建线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run1) object:nil];
//02启动线程
[thread start];
self.thread = thread;
}
- (IBAction)goOnBtnClick:(id)sender {
//让之前创建的子线程继续执行任务,主线程->子线程
[self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:YES];
}
//该方法执行完毕,线程对象就会进入到死亡状态
- (void)run1{
NSLog(@"run1 ------ %@",[NSThread currentThread]);
//01子线程的runLoop需要手动创建 + 启动
//02runLoop启动之后,选择运行模式(默认),内部会判断运行模式是否为空
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
//03往运行模式中添加事件(source|timer)
// [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(timerRun) userInfo:nil repeats:YES];
// source(port|timer|selector)
//04为默认的运行模式添加port事件,目的是让运行模式不为空,把RunLoop开启起来
// NSPort *port= [[NSPort alloc] init];
NSPort *port = [NSPort port];
[runLoop addPort:port forMode:NSRunLoopCommonModes];
[runLoop run];//内部指定的原型模式为默认(NSDefaultRunLoopMode)
NSLog(@"end ---------");
}
- (void)run2{
NSLog(@"run2 ------ %@",[NSThread currentThread]);
}
- (void)timerRun{
NSLog(@"%s",__func__);
}
@end
- 有关
RunLoop
内部的自动释放池
a.自动释放池第一次创建:是在
RunLoop
启动的时候。
b.自动释放池最后一次销毁:是在RunLoop
退出的时候。
c.自动释放池其他事件的创建和销毁:是在RunLoop
即将进入到休眠
的时候,会把之前的自动释放池销毁,重新创建一个自动释放池。
RunLoop
相关知识希望给朋友们带来一丢丢的帮助,如有问题敬请批正!!