通常來講,一個線程只能執行一個任務,執行完就會退出,若是咱們須要一種機制,讓線程能隨時處理時間但並不退出,那麼 RunLoop 就是這樣的一個機制。Runloop是事件接收和分發機制的一個實現。objective-c
RunLoop其實是一個對象,這個對象在循環中用來處理程序運行過程當中出現的各類事件(好比說觸摸事件、UI刷新事件、定時器事件、Selector事件),從而保持程序的持續運行;並且在沒有事件處理的時候,會進入睡眠模式,從而節省CPU資源,提升程序性能。編程
簡單的說run loop是事件驅動的一個大循環,以下代碼所示:bash
int main(int argc, char * argv[]) {
//程序一直運行狀態
while (AppIsRunning) {
//睡眠狀態,等待喚醒事件
id whoWakesMe = SleepForWakingUp();
//獲得喚醒事件
id event = GetEvent(whoWakesMe);
//開始處理事件
HandleEvent(event);
}
return 0;
}
複製代碼
程序一啓動就會開一個主線程,主線程一開起來就會跑一個主線程對應的Runloop, Runloop保證主線程不會被銷燬,也就保證了程序的持續運行。不光iOS,在其餘的編程平臺,Android, Windows等都有一個相似Runloop的機制保證程序的持續運行。網絡
GCD, mach kernel, block, pthread併發
NSTimer, UIEvent, Autorelease, NSObject(NSDelayedPerforming), NSObject(NSThreadPerformAddition), CADisplayLink, CATransition, CAAnimation, dispatch_get_main_queue() (GCD 中dispatch到main queue的block會被dispatch到main Runloop中執行), NSPort, NSURLConnection, AFNetworking(這個第三方網絡請求框架使用在開啓新線程中添加本身到Runloop監聽事件)app
程序運行起來時,當什麼操做都沒有作的時候,Runloop告訴CPU, 如今沒有事情作,我要去休息, 這時CPU就會將資源釋放出來去作其餘的事情,當有事情作的時候Runloop就會立馬起來去作事情。框架
iOS 程序的入口是 main 函數異步
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
複製代碼
程序主線程一開起來,就會跑一個和主線程對應的Runloop, 那麼Runloop必定是在程序的入口main函數中開啓。async
堆棧最底層是start(dyld),往上依次是main,UIApplication(main.m) -> GSEventRunModal(Graphic Services) -> RunLoop(包含CFRunLoopRunSpecific,__CFRunLoopRun,__CFRunLoopDoSouces0,CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION) -> Handle Touch Event函數
CFRunLoop開源代碼:http://opensource.apple.com/source/CF/CF-855.17/
Runloop 源碼:
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
複製代碼
咱們發現RunLoop確實是do while經過判斷result的值實現的。所以,咱們能夠把RunLoop當作一個死循環。若是沒有RunLoop,UIApplicationMain函數執行完畢以後將直接返回,也就沒有程序持續運行一說了。
執行順序的僞代碼:
int32_t __CFRunLoopRun()
{
// 通知即將進入runloop
__CFRunLoopDoObservers(KCFRunLoopEntry);
do
{
// 通知將要處理timer和source
__CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(kCFRunLoopBeforeSources);
// 處理非延遲的主線程調用
__CFRunLoopDoBlocks();
// 處理Source0事件
__CFRunLoopDoSource0();
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks();
}
/// 若是有 Source1 (基於port) 處於 ready 狀態,直接處理這個 Source1 而後跳轉去處理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort();
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的線程即將進入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
// GCD dispatch main queue
CheckIfExistMessagesInMainDispatchQueue();
// 即將進入休眠
__CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);
// 等待內核mach_msg事件
mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts();
// 等待。。。
// 從等待中醒來
__CFRunLoopDoObservers(kCFRunLoopAfterWaiting);
// 處理因timer的喚醒
if (wakeUpPort == timerPort)
__CFRunLoopDoTimers();
// 處理異步方法喚醒,如dispatch_async
else if (wakeUpPort == mainDispatchQueuePort)
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
// 處理Source1
else
__CFRunLoopDoSource1();
// 再次確保是否有同步的方法須要調用
__CFRunLoopDoBlocks();
} while (!stop && !timeout);
// 通知即將退出runloop
__CFRunLoopDoObservers(CFRunLoopExit);
}
複製代碼
RunLoop對象包括Fundation中的NSRunLoop對象和CoreFoundation中的CFRunLoopRef對象。由於Fundation框架是基於CFRunLoopRef的封裝,所以咱們學習RunLoop仍是要研究CFRunLoopRef 源碼。
//Foundation
[NSRunLoop currentRunLoop]; // 得到當前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 得到主線程的RunLoop對象
//Core Foundation
CFRunLoopGetCurrent(); // 得到當前線程的RunLoop對象
CFRunLoopGetMain(); // 得到主線程的RunLoop對象
複製代碼
值的注意的是子線程中的runloop不是默認開啓的,須要手動開啓,當調用 [NSRunLoop currentRunLoop] 時,若已存在當前線程的runloop返回,若不存在建立一個新的runloop對象再返回。
CFRunloopRef 源碼
// 建立字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 建立主線程 根據傳入的主線程建立主線程對應的RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存主線程 將主線程-key和RunLoop-Value保存到字典中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
複製代碼
Apple 不容許直接建立Runloop, 它只提供了兩個自動獲取的函數: CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。 CFRunLoopRef源碼:
/// 用DefaultMode啓動
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
/// 用指定的Mode啓動,容許設置RunLoop超時時間
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
/// RunLoop的實現
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
/// 首先根據modeName找到對應mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 若是mode裏沒有source/timer/observer, 直接返回。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
/// 1. 通知 Observers: RunLoop 即將進入 loop。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
/// 內部函數,進入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
/// 2. 通知 Observers: RunLoop 即將觸發 Timer 回調。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: RunLoop 即將觸發 Source0 (非port) 回調。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
/// 執行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 觸發 Source0 (非port) 回調。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
/// 執行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 5. 若是有 Source1 (基於port) 處於 ready 狀態,直接處理這個 Source1 而後跳轉去處理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的線程即將進入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
/// 7. 調用 mach_msg 等待接受 mach_port 的消息。線程將進入休眠, 直到被下面某一個事件喚醒。
/// • 一個基於 port 的Source 的事件。
/// • 一個 Timer 到時間了
/// • RunLoop 自身的超時時間到了
/// • 被其餘什麼調用者手動喚醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
}
/// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
/// 收到消息,處理消息。
handle_msg:
/// 9.1 若是一個 Timer 到時間了,觸發這個Timer的回調。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
/// 9.2 若是有dispatch到main_queue的block,執行block。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
/// 9.3 若是一個 Source1 (基於port) 發出事件了,處理這個事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
/// 執行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 進入loop時參數說處理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出傳入參數標記的超時時間了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部調用者強制中止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一個都沒有了
retVal = kCFRunLoopRunFinished;
}
/// 若是沒超時,mode裏沒空,loop也沒被中止,那繼續loop。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即將退出。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
複製代碼
能夠看出,線程和 RunLoop 之間是一一對應的,其關係是保存在一個全局的 Dictionary 裏。線程剛建立時並無 RunLoop,若是你不主動獲取,那它一直都不會有。RunLoop 的建立是發生在第一次獲取時,RunLoop 的銷燬是發生在線程結束時。你只能在一個線程的內部獲取其 RunLoop(主線程除外)。
[NSRunLoop currentRunLoop];方法調用時,會先看一下字典裏有沒有存子線程相對用的RunLoop,若是有則直接返回RunLoop,若是沒有則會建立一個,並將與之對應的子線程存入字典中。
Core Foundation中關於RunLoop的5個類:
CFRunLoopRef //得到當前RunLoop和主RunLoop
CFRunLoopModeRef //運行模式,只能選擇一種,在不一樣模式中作不一樣的操做
CFRunLoopSourceRef //事件源,輸入源
CFRunLoopTimerRef //定時器時間
CFRunLoopObserverRef //觀察者
複製代碼
一個Runloop包含若干個Mode, 每一個Mode又包含若干個Source / Timer / Observer. 每次調用Runloop 的主函數時,只能指定其中一個Mode, 這個Mode被稱做 CurrentMode. 若是須要切換Mode, 只能退出Loop, 再從新指定一個Mode進入。這樣作主要是爲了分隔開不一樣組的 Source/Timer/Observer, 讓其互不影響。
系統默認註冊了 5 個Mode, 其中常見的有第 1,2 種:
1. kCFRunLoopDefaultMode:App的默認Mode,一般主線程是在這個Mode下運行
2. UITrackingRunLoopMode:界面跟蹤 Mode,用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其餘 Mode 影響
3. UIInitializationRunLoopMode: 在剛啓動 App 時第進入的第一個 Mode,啓動完成後就再也不使用
4. GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,一般用不到
5. kCFRunLoopCommonModes: 這是一個佔位用的Mode,做爲標記kCFRunLoopDefaultMode和UITrackingRunLoopMode用,並非一種真正的Mode
複製代碼
上面的Source/Timer/Observer 被統稱爲 model item, 一個item 能夠被同時加入多個 Mode. 但一個item被重複加入同一個mode時是不會有效果的。若是一個mode中一個item都沒有,則Runloop會直接退出,不進入循環。
Mode 間切換 咱們平時在開發中必定遇到過,當咱們使用NSTimer每一段時間執行一些事情時滑動UIScrollView,NSTimer就會暫停,當咱們中止滑動之後,NSTimer又會從新恢復的狀況,咱們經過一段代碼來看一下:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
// 加入到RunLoop中才能夠運行
// 1. 把定時器添加到RunLoop中,而且選擇默認運行模式NSDefaultRunLoopMode = kCFRunLoopDefaultMode
// [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 當textFiled滑動的時候,timer失效,中止滑動時,timer恢復
// 緣由:當textFiled滑動的時候,RunLoop的Mode會自動切換成UITrackingRunLoopMode模式,所以timer失效,當中止滑動,RunLoop又會切換回NSDefaultRunLoopMode模式,所以timer又會從新啓動了
// 2. 當咱們將timer添加到UITrackingRunLoopMode模式中,此時只有咱們在滑動textField時timer纔會運行
// [[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// 3. 那個如何讓timer在兩個模式下均可以運行呢?
// 3.1 在兩個模式下都添加timer 是能夠的,可是timer添加了兩次,並非同一個timer
// 3.2 使用站位的運行模式 NSRunLoopCommonModes標記,凡是被打上NSRunLoopCommonModes標記的均可以運行,下面兩種模式被打上標籤
//0 : <CFString 0x10b7fe210 [0x10a8c7a40]>{contents = "UITrackingRunLoopMode"}
//2 : <CFString 0x10a8e85e0 [0x10a8c7a40]>{contents = "kCFRunLoopDefaultMode"}
// 所以也就是說若是咱們使用NSRunLoopCommonModes,timer能夠在UITrackingRunLoopMode,kCFRunLoopDefaultMode兩種模式下運行
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
NSLog(@"%@",[NSRunLoop mainRunLoop]);
}
-(void)show
{
NSLog(@"-------");
}
複製代碼
由上述代碼能夠看出,NSTimer無論用是由於Mode的切換,由於若是咱們在主線程使用定時器,此時RunLoop的Mode爲kCFRunLoopDefaultMode,即定時器屬於kCFRunLoopDefaultMode,那麼此時咱們滑動ScrollView時,RunLoop的Mode會切換到UITrackingRunLoopMode,所以在主線程的定時器就不在管用了,調用的方法也就再也不執行了,當咱們中止滑動時,RunLoop的Mode切換回kCFRunLoopDefaultMode,全部NSTimer就又管用了。
使用GCD也能夠建立計時器,並且更爲精確:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//建立隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//1.建立一個GCD定時器
/*
第一個參數:代表建立的是一個定時器
第四個參數:隊列
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 須要對timer進行強引用,保證其不會被釋放掉,纔會按時調用block塊
// 局部變量,讓指針強引用
self.timer = timer;
//2.設置定時器的開始時間,間隔時間,精準度
/*
第1個參數:要給哪一個定時器設置
第2個參數:開始時間
第3個參數:間隔時間
第4個參數:精準度 通常爲0 在容許範圍內增長偏差可提升程序的性能
GCD的單位是納秒 因此要*NSEC_PER_SEC
*/
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
//3.設置定時器要執行的事情
dispatch_source_set_event_handler(timer, ^{
NSLog(@"---%@--",[NSThread currentThread]);
});
// 啓動
dispatch_resume(timer);
}
複製代碼
Source分爲兩種:
Source0:非基於Port的 用於用戶主動觸發的事件(點擊button 或點擊屏幕) Source1:基於Port的 經過內核和其餘線程相互發送消息(與內核相關) 注意:Source1在處理的時候會分發一些操做給Source0去處理
NSTimer是對RunLoopTimer的封裝
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;
複製代碼
CFRunLoopObserverRef是觀察者,可以監聽RunLoop的狀態改變。 咱們直接來看代碼,給RunLoop添加監聽者,監聽其運行狀態:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//建立監聽者
/*
第一個參數 CFAllocatorRef allocator:分配存儲空間 CFAllocatorGetDefault()默認分配
第二個參數 CFOptionFlags activities:要監聽的狀態 kCFRunLoopAllActivities 監聽全部狀態
第三個參數 Boolean repeats:YES:持續監聽 NO:不持續
第四個參數 CFIndex order:優先級,通常填0便可
第五個參數 :回調 兩個參數observer:監聽者 activity:監聽的事件
*/
/*
全部事件
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source
kCFRunLoopBeforeWaiting = (1UL << 5), //即將進入休眠
kCFRunLoopAfterWaiting = (1UL << 6),// 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7),// 即將退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop進入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop要處理Timers了");
break;
case kCFRunLoopBeforeSources:
NSLog(@"RunLoop要處理Sources了");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"RunLoop要休息了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"RunLoop醒來了");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop退出了");
break;
default:
break;
}
});
// 給RunLoop添加監聽者
/*
第一個參數 CFRunLoopRef rl:要監聽哪一個RunLoop,這裏監聽的是主線程的RunLoop
第二個參數 CFRunLoopObserverRef observer 監聽者
第三個參數 CFStringRef mode 要監聽RunLoop在哪一種運行模式下的狀態
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
/*
CF的內存管理(Core Foundation)
凡是帶有Create、Copy、Retain等字眼的函數,建立出來的對象,都須要在最後作一次release
GCD原本在iOS6.0以前也是須要咱們釋放的,6.0以後GCD已經歸入到了ARC中,因此咱們不須要管了
*/
CFRelease(observer);
}
複製代碼
運行結果:
[NSRunLoop currentRunLoop]runUntilDate:<#(nonnull NSDate *)#>
[NSRunLoop currentRunLoop]runMode:<#(nonnull NSString *)#> beforeDate:<#(nonnull NSDate *)#>
複製代碼
NSTimer無論用是由於Mode的切換,由於若是咱們在主線程使用定時器,此時RunLoop的Mode爲kCFRunLoopDefaultMode,即定時器屬於kCFRunLoopDefaultMode,那麼此時咱們滑動ScrollView時,RunLoop的Mode會切換到UITrackingRunLoopMode,所以在主線程的定時器就不在管用了,調用的方法也就再也不執行了,當咱們中止滑動時,RunLoop的Mode切換回kCFRunLoopDefaultMode,全部NSTimer就又管用了。若想定時器繼續執行,須要將NSTimer 註冊爲 kCFRunLoopCommonModes 。
當調用 NSObject 的 performSelecter:afterDelay: 後,實際上其內部會建立一個 Timer 並添加到當前線程的 RunLoop 中。因此若是當前線程沒有 RunLoop,則這個方法會失效。 當調用 performSelector:onThread: 時,實際上其會建立一個 Timer 加到對應的線程去,一樣的,若是對應線程沒有 RunLoop 該方法也會失效。
事件響應: 蘋果註冊了一個 Source1 (基於 mach port 的) 用來接收系統事件,其回調函數爲 __IOHIDEventSystemClientQueueCallback()。 當一個硬件事件(觸摸/鎖屏/搖晃等)發生後,首先由 IOKit.framework 生成一個 IOHIDEvent 事件並由 SpringBoard 接收。SpringBoard 只接收按鍵(鎖屏/靜音等),觸摸,加速,接近傳感器等幾種 Event,隨後用 mach port 轉發給須要的App進程。隨後蘋果註冊的那個 Source1 就會觸發回調,並調用 _UIApplicationHandleEventQueue() 進行應用內部的分發。 _UIApplicationHandleEventQueue() 會把 IOHIDEvent 處理幷包裝成 UIEvent 進行處理或分發,其中包括識別 UIGesture/處理屏幕旋轉/發送給 UIWindow 等。一般事件好比 UIButton 點擊、touchesBegin/Move/End/Cancel 事件都是在這個回調中完成的。
手勢識別: 當上面的 _UIApplicationHandleEventQueue() 識別了一個手勢時,其首先會調用 Cancel 將當前的 touchesBegin/Move/End 系列回調打斷。隨後系統將對應的 UIGestureRecognizer 標記爲待處理。 蘋果註冊了一個 Observer 監測 BeforeWaiting (Loop即將進入休眠) 事件,這個Observer的回調函數是 _UIGestureRecognizerUpdateObserver(),其內部會獲取全部剛被標記爲待處理的 GestureRecognizer,並執行GestureRecognizer的回調。 當有 UIGestureRecognizer 的變化(建立/銷燬/狀態改變)時,這個回調都會進行相應處理。
當在操做 UI 時,好比改變了 Frame、更新了 UIView/CALayer 的層次時,或者手動調用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法後,這個 UIView/CALayer 就被標記爲待處理,並被提交到一個全局的容器去。
蘋果註冊了一個 Observer 監聽 BeforeWaiting(即將進入休眠) 和 Exit (即將退出Loop) 事件,回調去執行一個很長的函數:_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。這個函數裏會遍歷全部待處理的 UIView/CAlayer 以執行實際的繪製和調整,並更新 UI 界面。因此說界面刷新並不必定是在setNeedsLayout相關的代碼執行後馬上進行的。
系統就是經過@autoreleasepool {}這種方式來爲咱們建立自動釋放池的,一個線程對應一個runloop,系統會爲每個runloop隱式的建立一個自動釋放池,全部的autoreleasePool構成一個棧式結構,在每一個runloop結束時,當前棧頂的autoreleasePool會被銷燬,並且會對其中的每個對象作一次release(嚴格來講,是你對這個對象作了幾回autorelease就會作幾回release,不必定是一次),特別指出,使用容器的block版本的枚舉器的時候,系統會自動添加一個autoreleasePool
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 這裏被一個局部@autoreleasepool包圍着
}];
複製代碼
首先引入一個概念:Event_loop,通常一個線程執行完任務後就會退出,當須要保證該線程不退出,能夠經過相似如下方式:
function do_loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
複製代碼
開啓一個循環,保證線程不退出,這就是Event_loop模型。這是在不少操做系統中都使用的模型,例如OS/iOS中的RunLoop。這種模型最大的做用就是管理事件/消息,在有新消息到來時馬上喚醒處理,沒有待處理消息時線程休眠,避免資源浪費。
使用NSOperation+NSURLConnection併發模型都會面臨NSURLConnection下載完成前線程退出致使NSOperation對象接收不到回調的問題。AFNetWorking解決這個問題的方法是按照官方的guid NSURLConnection 上寫的NSURLConnection的delegate方法須要在connection發起的線程runloop中調用,因而AFNetWorking直接借鑑了Apple本身的一個Demo的實現方法單獨起一個global thread,內置一個runloop,全部的connection都由這個runloop發起,回調也是它接收,不佔用主線程,也不耗CPU資源。
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread =
[[NSThread alloc] initWithTarget:self
selector:@selector(networkRequestThreadEntryPoint:)
object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
複製代碼
相似的能夠用這個方法建立一個常駐服務的線程。
利用CFRunLoopMode的特性,能夠將圖片的加載放到NSDefaultRunLoopMode的mode裏,這樣在滾動UITrackingRunLoopMode這個mode時不會被加載而影響到。
UIImage *downloadedImage = ...;
[self.imageView performSelector:@selector(setImage:)
withObject:downloadedImage
afterDelay:0
inModes:@[NSDefaultRunLoopMode]];
複製代碼
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop));
while (1) {
for (NSString *mode in allModes) {
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
複製代碼
- (BOOL)runUntilBlock:(BOOL(^)())block timeout:(NSTimeInterval)timeout
{
__block Boolean fulfilled = NO;
void (^beforeWaiting) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) =
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
fulfilled = block();
if (fulfilled) {
CFRunLoopStop(CFRunLoopGetCurrent());
}
};
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, beforeWaiting);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// Run!
CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false);
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
CFRelease(observer);
return fulfilled;
}
複製代碼