原文連接swift
什麼是RunLoop?app
A RunLoop object processes input for sources such as mouse and keyboard events from the window system, Port objects, and NSConnection objects. A RunLoop object also processes Timer events. Your application neither creates or explicitly manages RunLoop objects. Each Thread object—including the application’s main thread—has an RunLoop object automatically created for it as needed. If you need to access the current thread’s run loop, you do so with the class method current. Note that from the perspective of RunLoop, Timer objects are not "input"—they are a special type, and one of the things that means is that they do not cause the run loop to return when they fire.oop
官方文檔優化
翻譯以下ui
RunLoop對象處理來自窗口系統,Port對象和NSConnection對象的源(如鼠標和鍵盤事件)的輸入。 RunLoop對象還處理Timer事件。 您的應用程序既不建立也不顯式管理RunLoop對象。每一個Thread對象(包括應用程序的主線程)都會根據須要自動爲其建立RunLoop對象。若是須要訪問當前線程的運行循環,可使用類方法current。 請注意,從RunLoop的角度來看,Timer對象不是「輸入」 - 它們是一種特殊類型,其中一個意思是它們不會致使運行循環在它們觸發時返回。atom
咱們都知道咱們的應用是一個進程,在應用程序週期中咱們大多都會開闢不一樣的線程來處理一些耗時的事情,在這裏面Runloop扮演的是什麼角色呢?spa
Runloop 能夠翻譯爲 事件循環
咱們能夠把其理解爲一個 while 循環線程
do {
// something
}while()
複製代碼
Runloop是和線程一一對應的,其中,主線程的Runloop會在應用啓動的時候進行啓動和綁定,其餘線程的Runloop要經過手動調用 [NSRunLoop currentRunLoop]
纔會生成翻譯
看一下Foundation中的NSRunLoop.h, 裏面有這幾個方法須要注意一下:code
@property (class, readonly, strong) NSRunLoop *currentRunLoop;
@property (class, readonly, strong) NSRunLoop *mainRunLoop;
@property (nullable, readonly, copy) NSRunLoopMode currentMode;
- (void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode;
- (void)addPort:(NSPort *)aPort forMode:(NSRunLoopMode)mode;
複製代碼
其中 currentRunLoop 表示獲取當前的Runloop ,mainRunLoop表明獲取主事件循環,這裏能夠看出,非主線程的Runloop必須在子線程內獲取,而mainRunLoop能夠在任意線程獲取。
NSRunLoopMode 在官方文檔中提到的有一會兒五個:
其中公開暴露出來的只有 NSDefaultRunLoopMode 和 NSRunLoopCommonModes ,在這裏面 NSRunLoopCommonModes 實際上包含了 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode,在須要追蹤包括scrollview滾動等事件的時候最好使用 NSRunLoopCommonModes
首先,咱們能夠在viewcontroller中的touchbegin方法打斷點,看一下該方法的調用棧
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
}
複製代碼
在斷點出發時咱們在控制檯查看
(lldb) thread backtrace
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
* frame #0: 0x000000010a1589a0 Library_test`-[ViewController touchesBegan:withEvent:](self=0x00007f7fda41adc0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x0000600002649440) at ViewController.m:35
frame #1: 0x0000000110e088e8 UIKitCore`forwardTouchMethod + 353
frame #2: 0x0000000110e08776 UIKitCore`-[UIResponder touchesBegan:withEvent:] + 49
frame #3: 0x0000000110e17dff UIKitCore`-[UIWindow _sendTouchesForEvent:] + 2052
frame #4: 0x0000000110e197a0 UIKitCore`-[UIWindow sendEvent:] + 4080
frame #5: 0x0000000110df7394 UIKitCore`-[UIApplication sendEvent:] + 352
frame #6: 0x0000000110ecc5a9 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 3054
frame #7: 0x0000000110ecf1cb UIKitCore`__handleEventQueueInternal + 5948
frame #8: 0x000000010cb87721 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #9: 0x000000010cb86f93 CoreFoundation`__CFRunLoopDoSources0 + 243
frame #10: 0x000000010cb8163f CoreFoundation`__CFRunLoopRun + 1263
frame #11: 0x000000010cb80e11 CoreFoundation`CFRunLoopRunSpecific + 625
frame #12: 0x00000001143891dd GraphicsServices`GSEventRunModal + 62
frame #13: 0x0000000110ddb81d UIKitCore`UIApplicationMain + 140
frame #14: 0x000000010a1590d0 Library_test`main(argc=1, argv=0x00007ffee5aa7000) at main.m:14
frame #15: 0x000000010e331575 libdyld.dylib`start + 1
(lldb)
複製代碼
能夠看到 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
的方法調用
其實這就是一個點擊的事件觸發了,經過runloop傳遞的例子,實際上全部的事件都會經過這個方法調用,最後觸發到咱們編寫的代碼, 理解了這個咱們接下來看看具體的應用。
在controller中添加一個timer
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSTimer *t;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_t = [NSTimer timerWithTimeInterval:1.0f repeats:true block:^(NSTimer * _Nonnull timer) {
NSLog(@"Timer Triggered");
}];
[[NSRunLoop currentRunLoop] addTimer:_t forMode:NSRunLoopCommonModes];
}
@end
複製代碼
在後臺進程處理邏輯,完成後經過modes避免在滑動視圖的時候返回
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelectorInBackground:@selector(someBussiness) withObject:nil];
}
- (void)someBussiness {
[self performSelector:@selector(bussinessFinished) withObject:nil afterDelay:0.0f inModes:@[NSDefaultRunLoopMode]];
}
- (void)bussinessFinished {
}
複製代碼
有時咱們想把一些邏輯所有放到自定義的線程中去處理,下面給出一個解決方案。
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, strong) NSPort *port;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.thread start];
}
- (void)selfThread {
@autoreleasepool {
[[NSRunLoop currentRunLoop] run];
}
}
- (NSThread *)thread {
if (!_thread) {
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(selfThread) object:nil];
}
return _thread;
}
- (NSPort *)port {
if (!_port) {
_port = [NSPort port];
}
return _port;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self performSelector:@selector(someBussiness) onThread:self.thread withObject:nil waitUntilDone:false];
}
- (void)someBussiness {
}
@end
複製代碼
運行以後貌似點擊屏幕並無反應?
這是由於咱們沒有爲Runloop加入source(輸入源),source能夠是timer,port。 其中,NSPort是一個描述通訊通道的抽象類,咱們能夠先使用port做爲輸入源來讓Runloop持續運行,改造這個方法
- (void)selfThread {
@autoreleasepool {
[[NSRunLoop currentRunLoop] addPort:self.port forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
}
}
複製代碼
如今咱們就能夠把須要執行的業務邏輯放入自定義的線程中執行了
還有其餘許多實際的應用,好比優化空閒狀體的利用、集合視圖的優化等等,這裏就很少敘述了