《NSRunLoop》

1. 简介

RunLoop从字面上解析,就是一直循环的跑,实际上它也是在一直在跑。通常来说,一个线程执行完一个任务后,线程就会退出销毁。但是我们可以通过RunLoop操作,使该线程常驻,在有任务的时候唤醒线程执行相应的任务,在没有任务执行的时候保存睡眠状态,时刻准备着任务的呼唤。想想为什么一个程序能够随时响应操作事件,原理也是一样的。

在iOS系统中,提供了NSRunLoopCFRunLoopRef两种对象来供我们操作。由于CFRunLoopRef是CoreFoundation提供的纯C函数的API,所有的这些都是线程安全的,可以被任何线程调用。而NSRunLoop是基于CFRunLoopRef封装的提供面向对象的API,这是API不是线程安全的,仅仅在run loop对应的那个线程上操作,如果添加一个输入源或者定时器给非当前线程的run loop会导致崩溃现象发生。

2. 开启RunLoop的时机

  • 使用端口或者自定义的输入源和其他线程通信
  • 在线程上使用定时器
  • 在cocoa应用中使用任意一个performSelector…方法
  • 使得线程不被杀死去做周期性任务

3. 与RunLoop相关的类

  • CFRunLoopRef RunLoop
  • CFRunLoopModeRef RunLoop的模式
  • CFRunLoopSourceRef RunLoop的输入源
  • CFRunLoopTimerRef 计时器
  • CFRunLoopObserverRef 观察者

3.1 CFRunLoopRef

3.1.1 获取RunLoop

//获取当前线程的RunLoop
CFRunLoopRef CFRunLoopGetCurrent(void);
//获取主线程的RunLoop
CFRunLoopRef CFRunLoopGetMain(void);复制代码

3.1.2 启动和停止RunLoop

//无限期的在默认模式下运行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);复制代码

3.1.3 输入源的管理

//将输入源添加到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);复制代码

3.1.4 模式管理

//将一个模式添加到一组运行循环,一旦添加到运行循环模式中,将无法删除
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFRunLoopMode mode);

//返回一个模式数组
CFArrayRef CFRunLoopCopyAllModes(CFRunLoopRef rl);

//复制当前的模式
CFRunLoopMode CFRunLoopCopyCurrentMode(CFRunLoopRef rl);复制代码

3.1.5 定时器

//将定时器添加到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);复制代码

3.1.6 获取typeID

CFTypeID CFRunLoopGetTypeID(void);复制代码

3.2 CFRunLoopModeRef

一个RunLoop包含N个CFRunLoopModeRef,每个Mode中又包含了N个CFRunLoopSourceRefCFRunLoopTimerRefCFRunLoopObserverRef。但是每次调用的时候,只能指定某一个Mode类型,若想切换Mode类型,必须退出RunLoop再创建RunLoop。

在NSRunLoop中,只提供了两种类型的模式

  • NSDefaultRunLoopMode
  • NSRunLoopCommonModes

3.3 CFRunLoopSourceRef

CFRunLoopSourceRef是产生事件的地方,分为两种:

  • Source0 只包含了一个回调,并不能主动触发事件。当使用的时候,需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件
  • Source1 包含了一个 mach_port 和一个回调,被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程

3.3.1 输入源信息

/* 创建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);复制代码

3.3.2 输入源回调

//当从循环中移除输入源时回调
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 //模式
);复制代码

3.3.3 数据类型

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;复制代码

3.4 CFRunLoopTimerRef

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;复制代码

3.5 CFRunLoopObserverRef

CFRunLoopObserverRef是观察者,关注着RunLoop的状态变化,RunLoop主要有六个状态

  • kCFRunLoopEntry 即将进入Loop
  • kCFRunLoopBeforeTimers 即将处理Timer
  • kCFRunLoopBeforeSources 即将处理Source
  • kCFRunLoopBeforeWaiting 进入睡眠
  • kCFRunLoopAfterWaiting 从睡眠中唤醒
  • kCFRunLoopExit 即将退出
  • kCFRunLoopAllActivities 以上的组合

3.5.1 函数类型

/* 创建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);复制代码

3.5.2 回调函数

/* 观察者对象被触发时回调
 * 
 * observer 观察者
 * activity 活动的阶段
 * info CFRunLoopObserverContext
 */
typedef void (*CFRunLoopObserverCallBack)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);复制代码

3.5.3 数据类型

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;复制代码

3.5.4 示范

- (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);
    }
}复制代码

4. 获取RunLoop

苹果并不允许我们创建RunLoop对象,只能通过api去获取当前的RunLoop对象。
NSRunLoop中,提供了两个属性帮助我们读取RunLoop对象

@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并返回对象

注意:应该避免使用GCD Global队列来创建RunLoop的常驻线程,因为在创建时可能出现全局队列的线程池满了的情况,因此GCD无法派发任务,很有可能造成奔溃。

4. RunLoop的运用

4.1 启动RunLoop的方式

必须给RunLoop添加一个输入源或者一个定时器才能在辅助线程上开启RunLoop,如果一个RunLoop没有任何源来监控,就会立刻退出。

//无条件运行RunLoop将线程放在一个永久的循环中,无法在定制模式下运行RunLoop
- (void)run;  
//在limitDate未到达之前,RunLoop会一直保持着运行
- (void)runUntilDate:(NSDate *)limitDate;
//RunLoop会被执行一次,时间达到或者时间处理完毕后,自动退出
- (void)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;复制代码

上述三个方法中,其实使用上面的两个方式启动保持一直运行,也就是不断的重复调用最后一个方法。

4.2 退出RunLoop的方式

  • 移除input sources或者timer
  • 添加一个定时源或者一个超时时间
  • 强制退出线程
  • 通过方法CFRunLoopStop来停止runloop

注意: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复制代码

4.3 RunLoop的输入源

4.3.1 自定义输入源

DYCustomSourceThread.h

#import 
#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 

@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复制代码

4.3.2 定时器

    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"执行timer事件");
        [timer invalidate];
    }];
    [_runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
    [_runLoop run];复制代码

4.3.3 基于端口的输入源

    NSMachPort *machPort = (NSMachPort *)[NSMachPort port];
    [_runLoop addPort:machPort forMode:NSRunLoopCommonModes];复制代码

demo

参考文章

NSRunLoop的退出方式
避免使用 GCD Global队列创建Runloop常驻线程
深入研究 Runloop 与线程保活
深入理解RunLoop

你可能感兴趣的:(《NSRunLoop》)