前言
2016年6月7號開始編寫CFRunLoop,并通過實現代碼
NSRunLoop
蘋果提供NSRunLoop類來實現RunLoop機制,NSRunLoop類聲明的對象,用于管理輸入源對象。一個NSRunLoop對象可以監聽輸入源包括鼠標、鍵盤事件,以及NSPort、NSConnection對象,以及NSRunLoop對象還可以處理NSTimer事件
應用程序不能創建或顯式管理NSRunLoop對象。每個NSThread對象以及應用程序的主線程都會根據自身需要自動創建它,如果想要訪問當前線程的RunLoop可以調用類方法+currentRunLoop,
NSRunLoop方法不是線程安全的,它的方法應該只存在當前線程的上下文,使用時應該避免NSRunLoop對象運行在不同線程,否則可能會導致意想不到的結果(CFRunLoopRef 是在 CoreFoundation 框架內的,它提供了純 C 函數的 API,所有這些 API 都是線程安全的。
NSRunLoop 是基于 CFRunLoopRef 的封裝,提供了面向對象的 API,但是這些 API 不是線程安全的。)
NSRunLoop使用方法
訪問NSRunLoop屬性和方法:
currentMode
+ currentRunLoop
- limitDateForMode:
+ mainRunLoop
- getCFRunLoop
管理計時器:
- addTimer:forMode:
管理端口
- addPort:forMode
- removePort:forMode:
運行一個循環
- (void)run
- 將接收到一個持久循環,在此期間它處理來自附加到輸入數據源的數據。
Discussion:
- 如果沒有連接的輸入源或計時器,運行RunLoop,該方法立即退出;否則,將通過runMode:beforeDate:方法設置Mode為NSDefaultRunLoopMode添加至接收器上。換句話說如果run方法有效,則開始無限循環運行處理傳入的輸入源和計時器。
- runMode:beforeDate:在指定模式下運行該循環,直到給定日期為止,返回True 運行循環運行和處理輸入源,如果是False,如果指定的超時值達到時,Run Loop將無法啟動
Discussion:
-
如果沒有連接的輸入源或計時器運行RunLoop,這種方法會立即退出并返回NO;否則,它返回后第一個輸入源進行處理或達到limitdate超時,手動刪除所有已知的輸入源和計時器并且保證運行循環不會立即退出
- (void)runUntilDate:(NSDate *)limitDate:運行循環,直到指定日期,在此期間,它處理來自附加輸入源的數據。
Discussion:
- (void)runUntilDate:(NSDate *)limitDate:運行循環,直到指定日期,在此期間,它處理來自附加輸入源的數據。
如果沒有連接的輸入源或計時器運行RunLoop,該方法會立即退出并返回NO;否則,它運行的接收機NSDefaultRunLoopMode通過反復調用runMode:beforeDate:直到指定的截止日期,手動刪除所有已知的輸入源和計時器并且保證運行循環不會立即退出
終止循環
Example:
在需要終止的地方調用shouldKeepRunning為NO
BOOL shouldKeepRunning = YES; // global
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
限制RunLoop終止時間
- (BOOL)runMode(NSString *)mode beforeDate:(NSDate *)limitDate
Discussion:
- 如果沒有輸入源或計時器連接到RunLoop,這個方法會立即退出并返回NO;否則,它將處理第一個輸入源直到達到終止時間limitDate。
- 手動刪除所有RunLoop已知的輸入源和計時器并不保證運行循環立即退出。
- 注意:計時器不被視為輸入源,可能在等待該方法時返回可能觸發多次
RunLoop調度和取消消息
// 此方法設置一個定時器,在下次運行循環開始執行aselector消息接收器,指定Mode參數設置定時器在該模式中運行
- performSelector:target:argument:order:modes:
Discussion
- 該方法設置一個計時器,在下次RunLoop時開始執行aselector消息接收器。
- 計時器被配置為所述模式參數中指定的模式下運行。當計時器觸發時,線程試圖從RunLoop列出消息,并進行選擇。如果成功觸發,RunLoop運行,并且在指定模式中。否則,計時器等待,直到RunLoop是在這些模式之一。
// 使用此方法取消的消息先前預定的使用的performSelector:target:argument:order:modes:,方法從運行循環的所有模式中移除執行請求。
- cancelPerformSelector:target:argument:
// 此方法取消與目標關聯的原定信息,忽略了對調度操作的選擇和參數。
- cancelPerformSelectorsWithTarget:
Run Loop Mode
NSRunLoop定義以下RunLoopMode運行循環模式。
處理除NSConnection以外的對象的輸入源模式。
NSDefaultRunLoopMode:當 UI 處于空閑狀態時,默認的 Mode 是 NSDefaultRunLoopMode(同 CF 中的 kCFRunLoopDefaultMode),同時也是 CF 中定義的 “空閑狀態 Mode”。當用戶啥也不點,此時也沒有什么網絡 IO 時,就是在這個 Mode 下。
NSRunLoopCommonModes:作為一組“共同”模式(組合Mode),使用該值運行監控的所有Run Loop Mode;
extern NSString* const NSDefaultRunLoopMode;
extern NSString* const NSRunLoopCommonModes;
CFRunLoop
CFRunLoop對象監控輸入任務的來源和分派已處理的任務對它進行控制,輸入源包括user input device(用戶輸入設備)、network connections(網絡連接), periodic or time-delayed events(周期或時滯事件), and asynchronous callbacks(異步回調).
提供三種類型的對象監視Run Loop
- 數據源對象:CFRunLoopSource
- 計時器對象:CFRunLoopTimer
- 觀察者對象:CFRunLoopObserver
處理這些對象時接收回調需要處理,首先將這些對象放到一個 RunLoop。
- CFRunLoopAddSource
- CFRunLoopAddTimer
- CFRunLoopAddObserver
(1)每個源、計時器和觀察者添加到Run Loop必須指定一個或多個運行循環模式(Run Loop Mode)相關聯。
(2)模式確定后,在給定的迭代過程中RunLoop處理哪些事件。每次運行循環執行時,它會在特定模式下運行。在這種模式下,運行循環過程只處理與源、計時器、觀察者與該模式相關聯的事件。
(3)默認運行循環方式由kCFRunLoopDefaultMode常數指定,當應用程序(或線程)處于閑置狀態時處理事件。
(4)RunLoop定義了其他的模式,并且可以在其他模式中執行運行循環,以限制源、計時器和觀察員被處理(例如以pseudo-mode為核心基礎,稱為公共模式,允許將多個模式與給定的源、計時器或觀察者關聯起來),還可以定義自己的自定義模式來限制事件處理,如果指定的是其他模式,可能處理滑動事件(UITableView/UIScrollView)時,限制其他模式(例如網絡IO處理)。
(5)添加一個模式的常見模式,使用CFRunLoopAddCommonMode函數。
功能
獲得一個運行循環
CFRunLoopGetCurrent:返回當前線程CFRunLoop對象
Discussion:
- 每個線程都有一個與之關聯的RunLoop
CFRunLoopGetMain:返回主CFRunLoop對象。
啟動和停止循環
CFRunLoopRun:在當前線程的使用默認模式運行
Discossion:
- 當前線程在默認模式(CFRunLoopDefaultMode)運行RunLoop,直到運行循環所有數據源和計時器是從默認的運行循環模式移動或者調用CFRunLoopStop函數停止RunLoop。
- 運行循環可以遞歸運行,可以從任何運行循環標注內調用CFRunLoopRun,并且當前線程調用棧上創建嵌套運行循環激活。
CFRunLoopRunResult CFRunLoopRunInMode ( CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled );當前線程的CFRunLoop對象運行在一個特定的模式。
Parameters:
- mode
指定RunLoopMode下運行。模式可以是任意CFString字符串。你并不需要顯式地創建一個運行循環模式,雖然運行循環模式需要包含至少一個源或定時器運行。 - seconds
設置時間的長短來運行RunLoop。如果為0,只有一個能通過RunLoop然后返回;;如果多個數據源或計時器時準備立即觸發,如果是0時無意義 - returnAfterSourceHandled
指示在處理一個源后運行循環是否應該退出的標志。如果為false,運行循環繼續,直到秒鐘已經過去了處理事件。
Discussion:
- 運行循環可以遞歸運行。可以從任何運行循環標注內調用CFRunLoopRunInMode和當前線程的調用堆棧中創建嵌套運行循環激活。并不局限于在模式就可以運行在一個標注。可以在任何可用的運行循環模式創建另一個運行的循環運行激活,包括任何模式已經運行在調用堆棧中較高的。(簡而言之:設置該Mode運行RunLoop優先級為最高),之所以這里詳細說明是后面閱讀CFRunLoop源碼有這一塊內容,方便理解。
返回碼為CFRunLoopRunInMode,確定運行循環退出的原因,以下返回值:
kCFRunLoopRunFinished。運行循環模式模式沒有源或計時器。
kCFRunLoopRunStopped。運行循環停止調用CFRunLoopStop函數。
kCFRunLoopRunTimedOut。時間間隔過去了(運行RunLoop超時)。
kCFRunLoopRunHandledSource。源已經被處理。這種退出條件,只有在RunLoop運行循環被告知只運行到一個源處理。
- 不能指定kCFRunLoopCommonModes常數模式作為參數,運行循環總是在特定的模式下運行。
- 在配置一個運行循環的觀察者想要的觀察員和希望在多個模式下運行的情況下,才指定的共同模式kCFRunLoopCommonModes。
CFRunLoopWakeUp:喚醒一個等待CFRunLoop對象
CFRunLoopStop:強制CFRunLoop對象停止運行。
CFRunLoopIsWaiting:返回一個布爾值來指示運行循環等待一個事件,如果是True,rl沒有事件要處理和阻塞,等待一個源或計時器處于就緒狀態;如果為False,rl要么不運行或正在處理的源程序,計時器,或觀察者。
管理觀察
CFRunLoopAddObserver 添加一個CFRunLoopObserver對象運行循環模式。
Discussion:
一個運行循環的觀察者只能在一次運行循環進行登記,它可以被添加到運行的循環中的多個運行循環模式。
如果rl已經包含在模式觀察者,這個函數什么也不做。CFRunLoopContainsObserver:返回一個布爾值,指示RunLoopMode是否包含一個特定的CFRunLoop觀察對象。
CFRunLoopRemoveObserver:移除RunLoopMode運行循環模式下的CFRunLoop觀察對象。
管理運行循環模式
CFRunLoopAddCommonMode:添加一個模式到設定的RunLoop的comomon modes(組合模式),RunLoopMode添加到Common modes模式,它不能被刪除。
CFRunLoopCopyAllModes:返回一個數組,其中包含所有CFRunLoop對象定義的模式。
CFStringRef CFRunLoopCopyCurrentMode ( CFRunLoopRef rl ):返回一個給定的運行循環當前正在運行的模式的名稱
管理計時器
void CFRunLoopAddTimer ( CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode ):添加一個CFRunLoopTimer對象到運行循環模式。
CFAbsoluteTime CFRunLoopGetNextTimerFireDate ( CFRunLoopRef rl, CFStringRef mode ):返回的時間是下一個就緒計時器
CFRunLoopRemoveTimer ( CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode ):從一個運行循環模式,刪除一個CFRunLoopTimer對象。
CFRunLoopContainsTimer ( CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode ):返回一個布爾值,用于顯示運行循環模式是否包含一個特定CFRunLoopTimer對象。
調度模塊
void CFRunLoopPerformBlock ( CFRunLoopRef rl, CFTypeRef mode, void (^block)(void) ):排入給定RunLoop ,在指定的RunLoopMode下runloop周期執行block任務。
- 當runloop在指定的模式下運行時,執行該塊的對象,可以使用此功能為手段,以移除當前的RunLoop操作通過指定到Cocoa框架下performSelector:onThread:withObject:waitUntilDone:方法另一個線程 和相關方法,還可以使用它作為一種替代機制,如把一個CFRunLoopTimer在另一個線程進行運行循環,或使用CFMessagePort線程之間傳遞信息。
線程與運行循環
- 運行循環是與線程關聯的基礎設施的一部分。運行循環是一個事件處理循環,用于調度工作,并協調接收傳入的事件。運行循環目的是讓你的線程在有工作的時候很忙,把你的線程放在沒有工作的時候休眠。
- 運行循環管理并不是完全自動的。還必須設計出你的線程的代碼,在適當的時間啟動運行循環,并響應傳入的事件。Cocoa和Core Foundation提供運行循環對象以幫助配置和管理線程的運行循環。應用程序不需要顯式地創建這些對象,每一個線程,包括應用程序的主線程,都有一個相關的運行循環對象。只有輔助線程需要明確地運行他們的運行循環。該應用程序框架自動設置和運行在主線程中運行循環的應用程序作為啟動過程的一部分。
RunLoop代碼邏輯
根據蘋果官方文檔對RunLoop代碼邏輯是這么描述的
A run loop is very much like its name sounds. It is a loop your thread enters and uses to run event handlers in response to incoming events. Your code provides the control statements used to implement the actual loop portion of the run loop—in other words, your code provides the while
or for
loop that drives the run loop. Within your loop, you use a run loop object to "run” the event-processing code that receives events and calls the installed handlers.
- 運行循環很像它的名字聽起來。 它是一個循環,線程進入和使用事件處理程序來響應傳入的事件。 代碼提供了控制語句用于實現的實際循環部分運行循環——換句話說,您的代碼提供了while或for循環來驅動運行循環。 在循環內,使用一個運行循環對象“運行”接收事件的處理代碼,并調用已安裝的處理程序。
代碼邏輯是這樣的:
- (void)loop {
initialize()
while (message != quit) {
// 獲取喚醒事件
id message = get_next_message()
// 處理事件
process_message(message)
}
}
- get_next_message()程序通常由操作系統提供,直到一個信息是可用的,循環只在輸入時有東西要處理才執行。
RunLoop的概念結構和各種各樣的來源如下圖所示。 輸入源提供異步事件到相應的處理程序,并造成runUntilDate:方法(稱為線程的相關NSRunLoop對象)(runUntilDate可以查閱NSRunLoop方法Discussion介紹)退出。 計時器來源提供事件處理程序,不會引起循環運行退出。
除了處理源的輸入外,運行循環也會產生關于循環行為的通知。注冊的運行循環的觀察員可以接收這些通知,并使用它們在線程中進行附加處理。
Run Loop Observers
當一個適當的異步或同步事件發生時,在特定位置的Run Loop Observers執行循環過程中,可以使用Run Loop Observers給準備線程或準備在休眠之前線程處理一個給定的事件。在運行循環中與下列事件關聯來觀察Run Loop Observers:
- 即將進入運行循環。
- 當運行循環將要處理一個計時器時。
- 當運行循環將要處理一個輸入源時。
- 當運行循環將要休眠。
- 當運行循環被喚醒時,在被喚醒之前,處理喚醒事件
- 退出運行循環。
蘋果官方提供CFRunLoopActivity枚舉來觀察RunLoop活動階段
enum CFRunLoopActivity {
kCFRunLoopEntry = (1 << 0), // 即將進入運行循環
kCFRunLoopBeforeTimers = (1 << 1), // 當運行循環將要處理一個計時器時。
kCFRunLoopBeforeSources = (1 << 2), // 當運行循環將要處理一個輸入源時。
kCFRunLoopBeforeWaiting = (1 << 5), // 當運行循環將要休眠。
kCFRunLoopAfterWaiting = (1 << 6), // 當運行循環被喚醒時,在被喚醒之前,處理喚醒事件
kCFRunLoopExit = (1 << 7), // 退出運行循環。
kCFRunLoopAllActivities = 0x0FFFFFFFU // 結合之前的所有階段
};
typedef enum CFRunLoopActivity CFRunLoopActivity;
通常使用CFRunLoopObserverCreate、CFRunLoopObserverCreateWithHandler來監聽RunLoop狀態,接收回調信息(常見于自動釋放池創建銷毀)
例子:
//
// ConfiguringRunLoopExample.m
// RunLoopExample
//
// Created by lmj on 16/6/7.
// Copyright ? 2016年 linmingjun. All rights reserved.
//
#import "ConfiguringRunLoopExample.h"
@interface ConfiguringRunLoopExample ()
@end
@implementation ConfiguringRunLoopExample
- (void)viewDidLoad {
[super viewDidLoad];
[NSThread detachNewThreadSelector:@selector(threadMain) toTarget:self withObject:nil];
// [self threadMain];
// [self performSelectorInBackground:@selector(threadMain) withObject:nil];
// [self performselector]
// [self performSelector:@selector(threadMain) withObject:nil afterDelay:0];
}
- (void)threadMain
{
// The application uses garbage collection, so no autorelease pool is needed.
// 獲得當前thread的Run loop
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// typedef void (*myObserver)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
// Create a run loop observer and attach it to the run loop.
// 設置Run loop observer的運行環境
CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
// 創建Run loop observer對象
// 第一個參數用于分配observer對象的內存
// 第二個參數用以設置observer所要關注的事件,詳見回調函數myRunLoopObserver中注釋
// 第三個參數用于標識該observer是在第一次進入run loop時執行還是每次進入run loop處理時均執行
// 第四個參數用于設置該observer的優先級
// 第五個參數用于設置該observer的回調函數
// 第六個參數用于設置該observer的運行環境
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver,&context);
// CFRunLoopObserverRef observer2 = CFRunLoopObserverCreateWithHandler
// (kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
// //The entrance of the run loop, before entering the event processing loop.
// //This activity occurs once for each call to CFRunLoopRun and CFRunLoopRunInMode
// });
if (observer)
{
// 將Cocoa的NSRunLoop類型轉換成Core Foundation的CFRunLoopRef類型
CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];
// 將新建的observer加入到當前thread的run loop
CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
}
// Create and schedule the timer.
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
NSInteger loopCount = 10;
do
{
// Run the run loop 10 times to let the timer fire.
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
loopCount--;
}
while (loopCount);
}
- (void)doFireTimer:(NSTimer *)timer {
NSLog(@"current thread: %@",[NSThread currentThread]);
NSLog(@"doFireTimer, %f", timer.timeInterval);
}
void myRunLoopObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
switch (activity) {
//The entrance of the run loop, before entering the event processing loop.
//This activity occurs once for each call to CFRunLoopRun and CFRunLoopRunInMode
case kCFRunLoopEntry:
NSLog(@"run loop entry");
break;
//Inside the event processing loop before any timers are processed
case kCFRunLoopBeforeTimers:
NSLog(@"run loop before timers");
break;
//Inside the event processing loop before any sources are processed
case kCFRunLoopBeforeSources:
NSLog(@"run loop before sources");
break;
//Inside the event processing loop before the run loop sleeps, waiting for a source or timer to fire.
//This activity does not occur if CFRunLoopRunInMode is called with a timeout of 0 seconds.
//It also does not occur in a particular iteration of the event processing loop if a version 0 source fires
case kCFRunLoopBeforeWaiting:
NSLog(@"run loop before waiting");
break;
//Inside the event processing loop after the run loop wakes up, but before processing the event that woke it up.
//This activity occurs only if the run loop did in fact go to sleep during the current loop
case kCFRunLoopAfterWaiting:
NSLog(@"run loop after waiting");
break;
//The exit of the run loop, after exiting the event processing loop.
//This activity occurs once for each call to CFRunLoopRun and CFRunLoopRunInMode
case kCFRunLoopExit:
NSLog(@"run loop exit");
break;
/*
A combination of all the preceding stages
case kCFRunLoopAllActivities:
break;
*/
default:
break;
}
}
@end
Parameters
** allocator **
- 分配器用來為新對象分配內存。傳遞NULL或kCFAllocatorDefault使用當前默認的分配。
activities
- 設置標志來標識運行循環,在此期間觀測被稱為活性階段。
repeats
- 一個標志識別觀察者是否只調用一次,或多次運行循環。如果repeats為false,它被稱為一次之后,觀察者是無效的,即使觀察者被安排在運行循環中的多個階段調用。
order
- 一個優先級索引,指示運行循環觀察者的順序。一般為0
**block **
- 設置該observer的回調函數
observer
- 正在執行的運行循環觀察器。
**activity **
- 循環的當前活動階段。
RunLoop 內部的邏輯:
每次運行它,線程的RunLoop處理等待事件,并生成通知附加到觀察者上。它的順序具體執行順序如下圖