在一個app中間有一個button,在你手觸摸屏幕點擊后,到這個button收到點擊事件,中間發(fā)生了什么?
此問題來自有 沒故事的卓同學的 4道過濾菜鳥的iOS面試題
這個問題的要點 :響應鏈 具體可以看下官方文檔的翻譯接下來從runloop層面大概聊一下如何進行事件響應的。
首選我們打斷點看一下點擊事件的堆棧信息
查看堆棧信息
我們可以通過三種方式查看堆棧信息
1.控制臺輸入bt
2.控制臺輸入thread backtrace
3.Xcode 底部選擇 show thread 如圖

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 12.1
* frame #0: 0x00000001009062a4 XXXX`-[XXXX Controller SelectAct:](self=0x0000000143d7bbd0, _cmd="SelectAct:", btn=0x0000000143d88130) at LocationController.m:224:4
frame #1: 0x00000001e150c454 UIKitCore`-[UIApplication sendAction:to:from:forEvent:] + 96
frame #2: 0x00000001e0f99d0c UIKitCore`-[UIControl sendAction:to:forEvent:] + 80
frame #3: 0x00000001e0f9a02c UIKitCore`-[UIControl _sendActionsForEvents:withEvent:] + 440
frame #4: 0x00000001e0f9902c UIKitCore`-[UIControl touchesEnded:withEvent:] + 568
frame #5: 0x0000000102b0e1d4 QMUIKit`__23+[UIControl(.block_descriptor=0x000000028385ef00, selfObject=0x0000000143d88130, touches=1 element, event=0x0000000281f15290) load]_block_invoke_2.74 at UIControl+QMUI.m:159:17
frame #6: 0x00000001e1545bac UIKitCore`-[UIWindow _sendTouchesForEvent:] + 2472
frame #7: 0x00000001e1546e10 UIKitCore`-[UIWindow sendEvent:] + 3156
frame #8: 0x00000001e152610c UIKitCore`-[UIApplication sendEvent:] + 340
frame #9: 0x00000001e15f4f68 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 1620
frame #10: 0x00000001e15f7960 UIKitCore`__handleEventQueueInternal + 4740
frame #11: 0x00000001e15f0450 UIKitCore`__handleHIDEventFetcherDrain + 152
frame #12: 0x00000001b43051f0 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
frame #13: 0x00000001b4305170 CoreFoundation`__CFRunLoopDoSource0 + 88
frame #14: 0x00000001b4304a54 CoreFoundation`__CFRunLoopDoSources0 + 176
frame #15: 0x00000001b42ff920 CoreFoundation`__CFRunLoopRun + 1040
frame #16: 0x00000001b42ff1f0 CoreFoundation`CFRunLoopRunSpecific + 436
frame #17: 0x00000001b6578584 GraphicsServices`GSEventRunModal + 100
frame #18: 0x00000001e150ad40 UIKitCore`UIApplicationMain + 212
frame #19: 0x00000001009577f0 XXXX `main(argc=1, argv=0x000000016f58b800) at main.mm:14:16
frame #20: 0x00000001b3dbebb4 libdyld.dylib`start + 4
我們可以看到堆棧信息出現(xiàn)了CoreFoundation框架的
__CFRunLoopDoSource0
__CFRunLoopRun
CFRunLoopRunSpecific
這些都是CFRunLoopRef內部實現(xiàn)方法(CFRunLoopRef 的代碼是開源的,你可以在這里 http://opensource.apple.com/tarballs/CF/ 下載到整個 CoreFoundation 的源碼來查看)而NSRunLoop是基于 CFRunLoopRef 的封裝,提供了面向對象的 API,但是這些 API 不是線程安全的。下面看下RunLoop內部邏輯
RunLoop 的內部邏輯

事件響應
蘋果注冊了一個 Source1 ( Source1 包含了一個 mach_port 和一個回調(函數(shù)指針),被用于通過內核和其他線程相互發(fā)送消息。這種 Source 能主動喚醒 RunLoop 的線程) 用來接收系統(tǒng)事件,其回調函數(shù)為 __IOHIDEventSystemClientQueueCallback()。
當一個硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后,首先由 IOKit.framework 生成一個 IOHIDEvent 事件并由 SpringBoard接收。這個過程的詳細情況可以參考這里。 SpringBoard只接收按鍵(鎖屏/靜音等),觸摸,加速,接近傳感器等幾種 Event,
隨后用 mach port 轉發(fā)給需要的App進程。
隨后蘋果注冊的那個 Source1 就會觸發(fā)回調,
之后在回調__IOHIDEventSystemClientQueueCallback()內觸發(fā)的 Source0,
Source0 再觸發(fā)的_UIApplicationHandleEventQueue() 進行應用內部的分發(fā)。
_UIApplicationHandleEventQueue() 會把 IOHIDEvent處理并包裝成 UIEvent 進行處理或分發(fā),其中包括識別 UIGesture/處理屏幕旋轉/發(fā)送給 UIWindow等。通常事件比如 UIButton 點擊、touchesBegin/Move/End/Cancel 事件都是在這個回調中完成的。
需要更深理解可以直接看深入理解RunLoop