1. 簡介
RunLoop從字面上解析,就是一直迴圈的跑,實際上它也是在一直在跑。通常來說,一個執行緒執行完一個任務後,執行緒就會退出銷燬。但是我們可以通過RunLoop操作,使該執行緒常駐,在有任務的時候喚醒執行緒執行相應的任務,在沒有任務執行的時候儲存睡眠狀態,時刻準備著任務的呼喚。想想為什麼一個程式能夠隨時響應操作事件,原理也是一樣的。
在iOS系統中,提供了NSRunLoop
和CFRunLoopRef
兩種物件來供我們操作。由於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個CFRunLoopSourceRef
、CFRunLoopTimerRef
、CFRunLoopObserverRef
。但是每次呼叫的時候,只能指定某一個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, ¤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複製程式碼
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];複製程式碼
參考文章
NSRunLoop的退出方式
避免使用 GCD Global佇列建立Runloop常駐執行緒
深入研究 Runloop 與執行緒保活
深入理解RunLoop