《NSRunLoop》

1. 簡介

RunLoop從字面上解析,就是一直循環的跑,實際上它也是在一直在跑。一般來講,一個線程執行完一個任務後,線程就會退出銷燬。可是咱們能夠經過RunLoop操做,使該線程常駐,在有任務的時候喚醒線程執行相應的任務,在沒有任務執行的時候保存睡眠狀態,時刻準備着任務的呼喚。想一想爲何一個程序可以隨時響應操做事件,原理也是同樣的。html

在iOS系統中,提供了NSRunLoopCFRunLoopRef兩種對象來供咱們操做。因爲CFRunLoopRef是CoreFoundation提供的純C函數的API,全部的這些都是線程安全的,能夠被任何線程調用。而NSRunLoop是基於CFRunLoopRef封裝的提供面向對象的API,這是API不是線程安全的,僅僅在run loop對應的那個線程上操做,若是添加一個輸入源或者定時器給非當前線程的run loop會致使崩潰現象發生。ios

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。git

在NSRunLoop中,只提供了兩種類型的模式github

  • NSDefaultRunLoopMode
  • NSRunLoopCommonModes

3.3 CFRunLoopSourceRef

CFRunLoopSourceRef是產生事件的地方,分爲兩種:api

  • 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對象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沒法派發任務,頗有可能形成奔潰。

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 <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, &currentRunLoopObserver, &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複製代碼

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

相關文章
相關標籤/搜索