RunLoop从字面上解析,就是一直循环的跑,实际上它也是在一直在跑。一般来讲,一个线程执行完一个任务后,线程就会退出销毁。可是咱们能够经过RunLoop操做,使该线程常驻,在有任务的时候唤醒线程执行相应的任务,在没有任务执行的时候保存睡眠状态,时刻准备着任务的呼唤。想一想为何一个程序可以随时响应操做事件,原理也是同样的。html
在iOS系统中,提供了NSRunLoop
和CFRunLoopRef
两种对象来供咱们操做。因为CFRunLoopRef
是CoreFoundation提供的纯C函数的API,全部的这些都是线程安全的,能够被任何线程调用。而NSRunLoop
是基于CFRunLoopRef
封装的提供面向对象的API,这是API不是线程安全的,仅仅在run loop对应的那个线程上操做,若是添加一个输入源或者定时器给非当前线程的run loop会致使崩溃现象发生。ios
//获取当前线程的RunLoop
CFRunLoopRef CFRunLoopGetCurrent(void);
//获取主线程的RunLoop
CFRunLoopRef CFRunLoopGetMain(void);复制代码
//无限期的在默认模式下运行RunLoop,直到运行循环中止,或者将全部源和定时器从默认运行循环模式中移除
void CFRunLoopRun(void);
/* 在特定的模式运行RunLoop * * mode 运行的模式 * seconds 运行循环的时间 * returnAfterSourceHandled 处理一个源的循环后是否应该退出 */
CFRunLoopRunResult CFRunLoopRunInMode(CFRunLoopMode mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);
//唤醒等待的RunLoop,当处于睡眠时,若是没有输入源或者定时器的触发,将一直处于睡眠,假若运行循环被修改,如添加新的输入源,则须要唤醒RunLoop处理事件
void CFRunLoopWakeUp(CFRunLoopRef rl);
//中止运行RunLoop
void CFRunLoopStop(CFRunLoopRef rl);
//运行循环是否在等待事件
Boolean CFRunLoopIsWaiting(CFRunLoopRef rl);复制代码
//将输入源添加到RunLoop
void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
/* 是否包含输入源 * * source 匹配的输入源 * mode 特有的模式下 */
Boolean CFRunLoopContainsSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
//从RunLoop中移除输入源
void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);复制代码
//将一个模式添加到一组运行循环,一旦添加到运行循环模式中,将没法删除
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFRunLoopMode mode);
//返回一个模式数组
CFArrayRef CFRunLoopCopyAllModes(CFRunLoopRef rl);
//复制当前的模式
CFRunLoopMode CFRunLoopCopyCurrentMode(CFRunLoopRef rl);复制代码
//将定时器添加到runLoop
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
//返回定时器下一个触发时间
CFAbsoluteTime CFRunLoopGetNextTimerFireDate(CFRunLoopRef rl, CFRunLoopMode mode);
//移除定时器
void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
//是否包含定时器
Boolean CFRunLoopContainsTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);复制代码
CFTypeID CFRunLoopGetTypeID(void);复制代码
一个RunLoop包含N个CFRunLoopModeRef
,每一个Mode中又包含了N个CFRunLoopSourceRef
、CFRunLoopTimerRef
、CFRunLoopObserverRef
。可是每次调用的时候,只能指定某一个Mode类型,若想切换Mode类型,必须退出RunLoop再建立RunLoop。git
在NSRunLoop中,只提供了两种类型的模式github
CFRunLoopSourceRef是产生事件的地方,分为两种:api
/* 建立CFRunLoopSource对象 * * allocator 分配内存适配器 使用NULL或者kCFAllocatorDefault * order 处理运行循环的优先级 * context runLoopSource上下文信息 */
CFRunLoopSourceRef CFRunLoopSourceCreate(CFAllocatorRef allocator, CFIndex order, CFRunLoopSourceContext *context);
/* 获取RunLoopSource上下文信息 * * source 输入源 * context 上下文信息的指针 */
void CFRunLoopSourceGetContext(CFRunLoopSourceRef source, CFRunLoopSourceContext *context);
/* 获取RunLoopSource优先级 * * source 输入源 */
CFIndex CFRunLoopSourceGetOrder(CFRunLoopSourceRef source);
//返回类型标识符
CFTypeID CFRunLoopSourceGetTypeID(void);
//移除输入源,阻止再次触发
void CFRunLoopSourceInvalidate(CFRunLoopSourceRef source);
//判断输入源是否有效
Boolean CFRunLoopSourceIsValid(CFRunLoopSourceRef source);
//发送输入源信息,并标记为准备启动
void CFRunLoopSourceSignal(CFRunLoopSourceRef source);复制代码
//当从循环中移除输入源时回调
typedef void (*CFRunLoopCancelCallBack) (
void *info, //CFRunLoopSourceContext
CFRunLoopRef rl, //正在移除输入源的RunLoop
CFStringRef mode //循环模式
);
//两个输入源是否相等的回调
typedef Boolean (*CFRunLoopEqualCallBack) (
const void *info1, // CFRunLoopSourceContext or CFRunLoopSourceContext1
const void *info2 // CFRunLoopSourceContext or CFRunLoopSourceContext1
);
//获取source1 输入源的mach端口
typedef mach_port_t (*CFRunLoopGetPortCallBack) (
void *info //CFRunLoopSourceContext1
);
//info对象hash值的回调
typedef CFHashCode (*CFRunLoopHashCallBack) (
const void *info
);
//接收到mach端口信息时回调
typedef void *(*CFRunLoopMachPerformCallBack) (
void *msg, //mach信息
CFIndex size, //信息的大小
CFAllocatorRef allocator, //内存分配
void *info //CFRunLoopSourceContext1
);
//收到RunLoopSource对象须要处理信息时回调
typedef void (*CFRunLoopPerformCallBack) (
void *info //CFRunLoopSourceContext
);
//将输入源添加到RunLoop时回调
typedef void (*CFRunLoopScheduleCallBack) (
void *info, //CFRunLoopSourceContext
CFRunLoopRef rl, //正在循环的RunLoop
CFStringRef mode //模式
);复制代码
typedef struct {
CFIndex version;//版本号必须为0
void * info;//指向数据的任意指针
const void *(*retain)(const void *info);//保留info指针时回调,能够为NULL
void (*release)(const void *info);//定义info指针执行是回调,能够为NULL
CFStringRef (*copyDescription)(const void *info);//定义info指针复制的回调,能够为NULL
Boolean (*equal)(const void *info1, const void *info2);//比较的回调,,能够为NULL
CFHashCode (*hash)(const void *info);//定义info指正hash的回调,能够为NULL
void (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);//添加输入源时回调,能够为NULL
void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);//移除输入源的回调,能够为NULL
void (*perform)(void *info);//运行循环执行触发时回调
} CFRunLoopSourceContext;复制代码
CFRunLoopTimerRef
主要是定时器,将定时器加入RunLoop中,到时间点到的时候,RunLoop唤醒定时器的执行方法数组
/* 使用块初始化RunLoop对象 * * allocator 内存适配器,使用NULL或者kCFAllocatorDefault * fireDate 首次触发的时间 * interval 定时器触发的间隔 * flags 该字段目前被忽略,0 * order 处理的优先级,定时器可忽略,值为0 * block 定时器触发的块 */
CFRunLoopTimerRef CFRunLoopTimerCreateWithHandler(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, void (^block)(CFRunLoopTimerRef timer));
/* 建立RunLoopTimer对象 * * allocator 内存适配器,使用NULL或者kCFAllocatorDefault * fireDate 首次触发的时间 * interval 定时器触发的间隔 * flags 该字段目前被忽略,0 * order 处理的优先级,定时器可忽略,值为0 * callout 定时器触发时的回调函数 * context 上下文信息,不须要时可为NULL */
CFRunLoopTimerRef CFRunLoopTimerCreate(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, CFRunLoopTimerCallBack callout, CFRunLoopTimerContext *context);
//判断RunLoopTimer是否重复
Boolean CFRunLoopTimerDoesRepeat(CFRunLoopTimerRef timer);
//联系上下文
void CFRunLoopTimerGetContext(CFRunLoopTimerRef timer, CFRunLoopTimerContext *context);
//返回时间间隔
CFTimeInterval CFRunLoopTimerGetInterval(CFRunLoopTimerRef timer);
//下一个触发时间
CFAbsoluteTime CFRunLoopTimerGetNextFireDate(CFRunLoopTimerRef timer);
//返回优先级
CFIndex CFRunLoopTimerGetOrder(CFRunLoopTimerRef timer);
//返回标识符
CFTypeID CFRunLoopTimerGetTypeID(void);
//移除RunLoopTimer,防止再次触发
void CFRunLoopTimerInvalidate(CFRunLoopTimerRef timer);
//判断是否有效
Boolean CFRunLoopTimerIsValid(CFRunLoopTimerRef timer);
//设置下一个启动日期
void CFRunLoopTimerSetNextFireDate(CFRunLoopTimerRef timer, CFAbsoluteTime fireDate);
/*定时器触发时回调 * * info CFRunLoopTimerContext */
typedef void (*CFRunLoopTimerCallBack)(CFRunLoopTimerRef timer, void *info);
typedef struct {
CFIndex version; //版本号,必须为0
void * info; //任意指针
const void *(*retain)(const void *info);//retain指针,能够为NULL
void (*release)(const void *info);//release指针能够为NULL
CFStringRef (*copyDescription)(const void *info); //定义info指针的描述回调,能够为NULL
} CFRunLoopTimerContext;复制代码
CFRunLoopObserverRef
CFRunLoopObserverRef
是观察者,关注着RunLoop的状态变化,RunLoop主要有六个状态安全
/* 建立RunLoopObserver观察者 * * allocator 内存分配器,使用NULL或kCFAllocatorDefault * activities 活动的类型 * repeats 一个或者多个经过运行循环调用的标志,若是为no,即便在多个阶段被调用也无效 * order 优先级别 * block 运行时调用 */
CFRunLoopObserverRef CFRunLoopObserverCreateWithHandler(CFAllocatorRef allocator, CFOptionFlags activities, Boolean repeats, CFIndex order, void (^block)(CFRunLoopObserverRef observer, CFRunLoopActivity activity));
/* 建立RunLoopObserver观察者 * * allocator 内存分配器,使用NULL或kCFAllocatorDefault * activities 活动的类型 * repeats 一个或者多个经过运行循环调用的标志 * order 优先级别 * callout 运行时回调 * context 联系上下文 */
CFRunLoopObserverRef CFRunLoopObserverCreate(CFAllocatorRef allocator, CFOptionFlags activities, Boolean repeats, CFIndex order, CFRunLoopObserverCallBack callout, CFRunLoopObserverContext *context);
//观察者是否重复
Boolean CFRunLoopObserverDoesRepeat(CFRunLoopObserverRef observer);
//返回循环的运行阶段
CFOptionFlags CFRunLoopObserverGetActivities(CFRunLoopObserverRef observer);
//联系上下文
void CFRunLoopObserverGetContext(CFRunLoopObserverRef observer, CFRunLoopObserverContext *context);
//获取优先级
CFIndex CFRunLoopObserverGetOrder(CFRunLoopObserverRef observer);
//获取类型标识符
CFTypeID CFRunLoopObserverGetTypeID(void);
//移除观察者
void CFRunLoopObserverInvalidate(CFRunLoopObserverRef observer);
//观察者是否可以触发
Boolean CFRunLoopObserverIsValid(CFRunLoopObserverRef observer);复制代码
/* 观察者对象被触发时回调 * * observer 观察者 * activity 活动的阶段 * info CFRunLoopObserverContext */
typedef void (*CFRunLoopObserverCallBack)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);复制代码
typedef struct {
CFIndex version; //版本号必须为0
void * info; //定义数据的任意指针
const void *(*retain)(const void *info); //retain指针,能够为NULL
void (*release)(const void *info); //release指针,能够为NULL
CFStringRef (*copyDescription)(const void *info); //info指针复制的回调
} CFRunLoopObserverContext;复制代码
- (void)createObserver{
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
if (observer) {
CFRunLoopRef cfLoop = [runLoop getCFRunLoop];
CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
}
}
void myRunLoopObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
NSString *threadName = [NSThread currentThread].name;
if (activity & kCFRunLoopEntry) {
NSLog(@"%@进入runloop",threadName);
}else if (activity & kCFRunLoopBeforeTimers){
NSLog(@"%@即将处理Timers",threadName);
}else if (activity & kCFRunLoopBeforeSources){
NSLog(@"%@即将处理Sources",threadName);
}else if (activity & kCFRunLoopBeforeWaiting){
NSLog(@"%@即将处理进入睡眠",threadName);
}else if (activity & kCFRunLoopAfterWaiting){
NSLog(@"%@从睡眠中唤醒",threadName);
}else if (activity & kCFRunLoopExit){
NSLog(@"%@退出runloop",threadName);
}
}复制代码
苹果并不容许咱们建立RunLoop对象,只能经过api去获取当前的RunLoop对象。
在NSRunLoop
中,提供了两个属性帮助咱们读取RunLoop对象bash
@property (class, readonly, strong) NSRunLoop *currentRunLoop;
@property (class, readonly, strong) NSRunLoop *mainRunLoop;复制代码
在CFRunLoopRef
中,也提供了两个方法获取CFRunLoopRef函数
CF_EXPORT CFRunLoopRef CFRunLoopGetCurrent(void);
CF_EXPORT CFRunLoopRef CFRunLoopGetMain(void);复制代码
RunLoop和线程时一一对象的关系,线程在建立的时候并不会建立RunLoop对象,只有主动获取线程中的RunLoop对象中,才会去检测线程中是否拥有RunLoop对象,有则返回该对象;若是没有则建立RunLoop并返回对象oop
注意
:应该避免使用GCD Global队列来建立RunLoop的常驻线程,由于在建立时可能出现全局队列的线程池满了的状况,所以GCD没法派发任务,颇有可能形成奔溃。
必须给RunLoop添加一个输入源或者一个定时器才能在辅助线程上开启RunLoop,若是一个RunLoop没有任何源来监控,就会马上退出。
//无条件运行RunLoop将线程放在一个永久的循环中,没法在定制模式下运行RunLoop
- (void)run;
//在limitDate未到达以前,RunLoop会一直保持着运行
- (void)runUntilDate:(NSDate *)limitDate;
//RunLoop会被执行一次,时间达到或者时间处理完毕后,自动退出
- (void)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;复制代码
上述三个方法中,其实使用上面的两个方式启动保持一直运行,也就是不断的重复调用最后一个方法。
注意
:CFRunLoopStop() 方法只会结束当前的 runMode:beforeDate: 调用,而不会结束后续的调用。
虽然有四种方式能够退出RunLoop循环,可是官方建议给RunLoop配置一个超时时间或者中止RunLoop来退出RunLoop。不建议使用移除runLoop的输入源和定时器来使RunLoop退出,由于有些系统程序给run loop增长输入源处理必要的事件。此时代码可能没有注意到这些输入源的存在,于是不能彻底移除掉这些输入源,导致RunLoop没法退出。
#import "ExitViewController.h"
#import "CustomThread.h"
@interface ExitViewController (){
NSRunLoop *_runLoop;
NSMachPort *_machPort;
__weak CustomThread *_thread;
BOOL _isStopRunLoop;
}
@end
@implementation ExitViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor grayColor];
CustomThread *thread = [[CustomThread alloc] initWithTarget:self selector:@selector(createRunLoop) object:nil];
[thread setName:@"com.donyau.thread"];
[thread start];
_thread = thread;
}
- (void)createRunLoop{
NSLog(@"当前线程%@",[NSThread currentThread]);
_runLoop = [NSRunLoop currentRunLoop];
CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
if (observer) {
CFRunLoopRef cfLoop = [_runLoop getCFRunLoop];
CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
}
[self runLoopStop];
NSLog(@"执行");
}
void myRunLoopObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
NSString *threadName = [NSThread currentThread].name;
if (activity & kCFRunLoopEntry) {
NSLog(@"%@进入runloop",threadName);
}else if (activity & kCFRunLoopBeforeTimers){
NSLog(@"%@即将处理Timers",threadName);
}else if (activity & kCFRunLoopBeforeSources){
NSLog(@"%@即将处理Sources",threadName);
}else if (activity & kCFRunLoopBeforeWaiting){
NSLog(@"%@即将处理进入睡眠",threadName);
}else if (activity & kCFRunLoopAfterWaiting){
NSLog(@"%@从睡眠中唤醒",threadName);
}else if (activity & kCFRunLoopExit){
NSLog(@"%@退出runloop",threadName);
}
}
#pragma mark - CFRunLoopStop
- (void)runLoopStop{
_machPort = (NSMachPort *)[NSMachPort port];
[_runLoop addPort:_machPort forMode:NSRunLoopCommonModes];
[self performSelector:@selector(logRunLoopStop:) withObject:@"1" afterDelay:4];
[self performSelector:@selector(logRunLoopStop:) withObject:@"2" afterDelay:8];
while (!_isStopRunLoop && [_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
}
}
- (void)logRunLoopStop:(NSString *)mesg{
NSLog(@"%@--%@",[NSThread currentThread],mesg);
_isStopRunLoop = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
}
@end复制代码
DYCustomSourceThread.h
#import <Foundation/Foundation.h>
#import "DYRunLoopSource.h"
@interface DYCustomSourceThread : NSThread
@property (nonatomic, strong) DYRunLoopSource *runLoopSource;
@end复制代码
DYCustomSourceThread.m
#import "DYCustomSourceThread.h"
#import "DYRunLoopSource.h"
@interface DYCustomSourceThread (){
}
@end
@implementation DYCustomSourceThread
void currentRunLoopObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
NSString *threadName = [NSThread currentThread].name;
if (activity & kCFRunLoopEntry) {
NSLog(@"%@进入runloop",threadName);
}else if (activity & kCFRunLoopBeforeTimers){
NSLog(@"%@即将处理Timers",threadName);
}else if (activity & kCFRunLoopBeforeSources){
NSLog(@"%@即将处理Sources",threadName);
}else if (activity & kCFRunLoopBeforeWaiting){
NSLog(@"%@即将处理进入睡眠",threadName);
}else if (activity & kCFRunLoopAfterWaiting){
NSLog(@"%@从睡眠中唤醒",threadName);
}else if (activity & kCFRunLoopExit){
NSLog(@"%@退出runloop",threadName);
}
}
-(void)main{
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
_runLoopSource = [[DYRunLoopSource alloc] init];
[_runLoopSource addToCurrentRunLoop];
CFRunLoopObserverContext context = {0, (__bridge void*)(self),NULL,NULL,NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ¤tRunLoopObserver, &context);
if (observer) {
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopAddObserver(runLoop, observer, kCFRunLoopDefaultMode);
}
[runLoop run];
}
}
@end复制代码
DYRunLoopSource.h
#import <Foundation/Foundation.h>
@interface DYRunLoopSource : NSObject
//将输入源添加到RunLoop
- (void)addToCurrentRunLoop;
//移除输入源
- (void)invalidate;
//唤醒RunLoop
- (void)wakeUpRunLoop;
@end复制代码
DYRunLoopSource.m
#import "DYRunLoopSource.h"
@interface DYRunLoopSource (){
CFRunLoopSourceRef _runLoopSource;
CFRunLoopRef _runLoop;
}
@end
@implementation DYRunLoopSource
#pragma mark 输入源添加进RunLoop时调用
void runLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFRunLoopMode mode){
NSLog(@"RunLoop添加输入源");
}
#pragma mark 将输入源从RunLoop移除时调用
void runLoopSourceCancelRoutine (void *info, CFRunLoopRef runLoopRef, CFStringRef mode){
NSLog(@"移除输入源");
}
#pragma mark 输入源须要处理信息时调用
void runLoopSourcePerformRoutine (void *info){
NSLog(@"输入源正在处理任务");
}
-(instancetype)init{
if (self = [super init]) {
CFRunLoopSourceContext context = {0,(__bridge void *)(self),NULL,NULL,NULL,NULL,NULL,&runLoopSourceScheduleRoutine,&runLoopSourceCancelRoutine,&runLoopSourcePerformRoutine};
_runLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
}
return self;
}
-(void)addToCurrentRunLoop{
_runLoop = CFRunLoopGetCurrent();
CFRunLoopAddSource(_runLoop, _runLoopSource, kCFRunLoopDefaultMode);
}
- (void)invalidate{
CFRunLoopRemoveSource(_runLoop, _runLoopSource, kCFRunLoopDefaultMode);
[self wakeUpRunLoop];
}
-(void)wakeUpRunLoop{
if (CFRunLoopIsWaiting(_runLoop) && CFRunLoopSourceIsValid(_runLoopSource)) {
CFRunLoopSourceSignal(_runLoopSource);
CFRunLoopWakeUp(_runLoop);
}
}
@end复制代码
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"执行timer事件");
[timer invalidate];
}];
[_runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
[_runLoop run];复制代码
NSMachPort *machPort = (NSMachPort *)[NSMachPort port];
[_runLoop addPort:machPort forMode:NSRunLoopCommonModes];复制代码
NSRunLoop的退出方式
避免使用 GCD Global队列建立Runloop常驻线程
深刻研究 Runloop 与线程保活
深刻理解RunLoop