RunLoop
从字面意思来看:跑圈、运动循环 基本用法:保持程序持续运行、处理App中的各种事件(触摸事件、定时器事件、SEL等等) 为什么需要它:节省CPU资源、 提高性能
如果没有RunLoop,程序在执行到7行就结束了。
代码语言:javascript复制int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
有Runloop后,程序就相当于一直在做循环
在这个循环内部不断地处理各种任务(比如Source、Timer、Observer)
代码语言:javascript复制int main(int argc, const char * argv[]) {
BOOL running = YES;
do{
// 执行各种任务,处理各种时间
}while (running);
return 0;
}
程序中的Runloop----UIApplicationMain
代码语言:javascript复制int main(int argc, char *argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
1.在UIApplicationMain函数内部就启动了一个RunLoop 2.UIApplicationMain函数一直没有返回,保持了程序的持续运行 3.这个默认启动的RunLoop是主线程关联的。 4.一个线程对应一个RunLoop,主线程的RunLoop默认已经启动 5.子线程的RunLoop得手动启动(调用run方法) 6.RunLoop只能选择一个Mode启动,如果当前Mode中没有任Source、Timer、Observer,那么就直接退出RunLoop
RunLoop里面有两套api用来访问和使用RunLoop 1、Foundation--NSRunLoop 2、Core Foundation --- CFRunloopRef 二者异同点: NSRunLoop和CFRunloopRef都代表RunLoop对象,NSRunLoop是对CFRunloopRef一层OC的封装
RunLoop与线程:
每条线程都有一个RunLoop对象,主线程默认已经创建好了,子线程需要主动创建 Runloop在第一次获取时创建,在线程结束后销毁。
代码语言:javascript复制 /* 1. Foundation */
// 获取当前线程
NSRunLoop *roop = [NSRunLoop currentRunLoop];
// 获取主线程
[NSRunLoop mainRunLoop];
/*2. Core Foundation */
// 获取当期线程
CFRunLoopGetCurrent();
// 获取主线程
CFRunLoopGetMain();
RunLoop相关类(Runloop中如果没有Source,Observre,Timer,Mode,就会结束)
代码语言:javascript复制 // Runloop对象
CFRunLoopRef;
// Runloop事件源(数据源)
CFRunLoopSourceRef;
// Runloop观察者
CFRunLoopObserverRef;
// Runloop时间源
CFRunLoopTimerRef;
// Runloop模式
CFRunloopModeRef;
Runloop里面相关类的互相关系-
Paste_Image.png
代码语言:javascript复制 // 获取当前Runloop的模式
NSString *runloopMode = [NSRunLoop currentRunLoop].currentMode;
1.同一时间只可以运行其中的一种model 2.切换Model只能退出Runloop,重新进入 3.系统默认注册了5个Mode
代码语言:javascript复制//App默认的Mode,通常主线程是在这个Mode下运行
NSDefaultRunLoopMode:
//界面追踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
UITrackingRunLoopMode
//这是一个占位用的Mode,不是一种正真的Mode
NSRunLoopCommonModes
//在刚启动App进入的第一个Mode,启动完成以后就不再使用
UIInitializationRunLoopMode
//接收系统事件的内部Mode,通常用不到
GSEventReceiveRunLoopMode
CFRunLoopTimerRef
CFRunLoopTimerRef是基于时间的触发器
CFRunLoopTimerRef基本上说就是NSTimer,它受RunLoop的Mode影响
代码语言:javascript复制NSTimer *time = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
// 定时器只运行在NSDefaultRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作
[[NSRunLoop currentRunLoop] addTimer:time forMode:NSDefaultRunLoopMode];
// 定时器会跑在标记为common modes的模式下
// 标记为common modes的模式:包含:UITrackingRunLoopMode和kCFRunLoopDefaultMode
[[NSRunLoop currentRunLoop] addTimer:time forMode:NSRunLoopCommonModes];
GCD不受RunLoop的Mode影响
代码语言:javascript复制 // 获取一个全局并发队列
/*
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 1. 设置定时器
// dispatchQueue :决定了将来回调的方法在哪里执行。
// dispatch_source_t timer 是一个OC对象
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 2.指定定时期开始的时间和间隔的时间
// DISPATCH_TIME_NOW :第2个参数:定时器开始时间 (也可以抽出来,设置间隔时间)
dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 1.0 *NSEC_PER_SEC);
// intervalInSeconds :第三个参数:定时器开始后的间隔时间
// leewayInSeconds:第四个参数:间隔精准度,0代标最精准,传入一个大于0的数,代表多少秒的范围是可以接收的,主要为了提高程序性能,积攒一定的时间,Runloop执行完任务会睡觉,这个方法让他多睡一会,积攒时间,任务也就相应多了一点,而后一起执行
// 开始时间
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
// 3.指定定时器回调方法
dispatch_source_set_event_handler(timer, ^{
NSLog(@"___________");
});
// 开启定时器
dispatch_resume(timer);
CFRunLoopSourceRef(事件源、输入源)
代码语言:javascript复制Port-Based Sources (端口)
Custom Input (自定义事件)
Cocoa Perform Selector Sources
按照函数的调用栈 Source0:非基于Port的 Source1:基于Port 通过内核和其他线程通信,接收分发系统事件
CFRunLoopObserverRef(观察者)
能够监听RunLoop的状态改变
Paste_Image.png
CFRunLoopObserverRef
代码语言:javascript复制typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 1
kCFRunLoopBeforeTimers = (1UL << 1), // 2
kCFRunLoopBeforeSources = (1UL << 2), // 4
kCFRunLoopBeforeWaiting = (1UL << 5), // 32
kCFRunLoopAfterWaiting = (1UL << 6), // 64
kCFRunLoopExit = (1UL << 7), // 128
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
创建一个Observer
代码语言:javascript复制 /*
第1个参数:如何给Observer分配存储空间
第2个参数:需要监听的状态类型/kCFRunLoopAllActivities监听所有状态
第3个参数:是否每次需要监听?
第4个参数:优先级(0)
第5个参数:监听状态改变后的回调
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
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;
}
});
/*
第1个参数:要给那个RunLoop添加观察者
第2个参数:添加的Observer对象
第3个参数:在那种模式下监听
*/
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
// 释放对象
CFRelease(observer);
由于ARC只管理Foundation框架的内容,所以我们在Core Foundation 框架创建的对象必须手动释放。 规律: 凡是带有copy、create、retain等字眼的函数,创建出来的CF对象,都需要在最后做一次release
官方对于RunLoop的解释:
Paste_Image.png
RunLoop处理逻辑,整理:自动释放池的生命周期
RunLoop在进入这个 kCFRunLoopBeforeWaiting时,会对自动释放池销毁
Paste_Image.png
Runloop:在开发中有什么作用?
1.NSTimer 2.ImageView的显示 3.PerformSelector 4.常驻线程 5.自动释放池
PerformSelector
代码语言:javascript复制- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
/*
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSString *> *)modes;
*/
// 指定模式下进行特定的操作
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"rightPic"] afterDelay:2.0 inModes:@[NSDefaultRunLoopMode]];
}
常驻线程: 默认情况下,一个线程只能使用一次,也就是只能执行一个操作 我们需要做得到就是强引用,保留线程,同时添加Source Or Timer,注:系统只会监测Source Or Timer,不会检查Observer
1.子线程的Runloop需要手动创建 2.子线程的Runloop需要手动开启 3.如果子线程的NSRunLoop没有设置Source Or Timer 那么子线程会立刻关闭。
代码语言:javascript复制- (void)ViewDidLoad
{
[super viewDidLoad];
SYThread *thread = [[SYThread alloc] initWithTarget:self selector:@selector(show) object:nil];
self.thread = thread;
[thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(test) onThread:self.thread withObject:@"时间计算" waitUntilDone:YES modes:@[NSRunLoopCommonModes]];
}
- (void)show
{
NSLog(@"%s", __func__);
// 这句话并没有意义,只是保证线程不死
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
// 上面线程不死,这句话永远不会打印
NSLog(@"-------$$$$");
}
- (void)test
{
NSLog(@"11111");
}