《NSRunLoop》

孤盞茗發表於2017-09-04

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