1.運行循環
1.1 每一個線程都有一個runloop,主線程的runloop是默認開啟的,子線程runloop默認是不開啟的
1.2 同一個方法中的代碼一般都在同一個運行循環中執行
1.3 runloop監聽UI界面的修改事件,待本次運行循環結束時,會統一將界面的修改渲染出來
1.4 點擊事件觸發結束后,立即結束本次運行循環
2.runloop的作用
2.1 保證程序不退出
2.2 負責監聽所有事件, 如: 手勢觸摸, 定時器, 網絡加載數據等
3.特性
3.1 沒有事件時, 會進入休眠(省電), 一旦監聽到事件, 就會立即響應
3.2 每一個線程都有一個runloop, 主線程的runloop是默認開啟的, 子線程的runloop是默認不開啟的
4.子線程的運行循環
4.1 子線程的運行循環默認是不開啟的
4.2 啟動運行循環后, 如果不停止運行循環, 不會執行后續的任何代碼, 會形成一個死循環
4.3 一旦停止了運行循環, 后續代碼才能執行, 執行完畢后, 線程被自動銷毀
5.三種啟動runloop的方式
通過[NSRunLoop currentRunLoop]或者CFRunLoopGetCurrent()可以獲取當前線程的runloop。
啟動一個runloop有以下三種方法:
- (void)run;
- (void)runUntilDate:(NSDate *)limitDate;
- (void)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
這三種方式無論通過哪一種方式啟動runloop,如果沒有一個輸入源或者timer附加于runloop上,runloop就會立刻退出。
(1) 第一種方式,runloop會一直運行下去,在此期間會處理來自輸入源的數據,并且會在NSDefaultRunLoopMode模式下重復調用runMode:beforeDate:方法;
(2) 第二種方式,可以設置超時時間,在超時時間到達之前,runloop會一直運行,在此期間runloop會處理來自輸入源的數據,并且也會在NSDefaultRunLoopMode模式下重復調用runMode:beforeDate:方法;
(3) 第三種方式,runloop會運行一次,超時時間到達或者第一個input source被處理,則runloop就會退出。
前兩種啟動方式會重復調用runMode:beforeDate:方法。
6.退出runloop的方式
第一種啟動方式的退出方法
文檔說,如果想退出runloop,不應該使用第一種啟動方式來啟動runloop。
如果runloop沒有input sources或者附加的timer,runloop就會退出。
雖然這樣可以將runloop退出,但是蘋果并不建議我們這么做,因為系統內部有可能會在當前線程的runloop中添加一些輸入源,所以通過手動移除input source或者timer這種方式,并不能保證runloop一定會退出。
第二種啟動方式runUntilDate:
可以通過設置超時時間來退出runloop。
第三種啟動方式runMode:beforeDate:
通過這種方式啟動,runloop會運行一次,當超時時間到達或者第一個輸入源被處理,runloop就會退出。
如果我們想控制runloop的退出時機,而不是在處理完一個輸入源事件之后就退出,那么就要重復調用runMode:beforeDate:,
具體可以參考蘋果文檔給出的方案,如下:
NSRunLoop *myLoop = [NSRunLoop currentRunLoop];
myPort = (NSMachPort *)[NSMachPort port];
[myLoop addPort:_port forMode:NSDefaultRunLoopMode];
BOOL isLoopRunning = YES; // global
while (isLoopRunning && [myLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
//關閉runloop的地方
- (void)quitLoop
{
isLoopRunning = NO;
CFRunLoopStop(CFRunLoopGetCurrent());
}
總結:
如果不想退出runloop可以使用第一種方式啟動runloop;
使用第二種方式啟動runloop,可以通過設置超時時間來退出;
使用第三種方式啟動runloop,可以通過設置超時時間或者使用CFRunLoopStop方法來退出。
7.c語言啟動和關閉runloop
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, assign) CFRunLoopRef runloop;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"timer start");
// 1. 創建timer, 并加入到runloop
// 默認將以 NSDefaultRunLoopMode 模式, 由系統添加到runloop
// self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
// 在子線程開啟timer,由于子線程的運行循環沒有啟動,所以沒法監聽timer事件
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
// 將timer手動添加到runloop
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
// 獲取當前的runloop
// self.runloop = CFRunLoopGetCurrent();
// 啟動runloop
// 啟動runloop方法一, 可以通過CFRunLoopStop(self.runloop)來停止runloop
// 啟動子線程的運行循環,這句代碼就是一個死循環!如果不停止運行循環,不會執行后續的任何代碼
CFRunLoopRun();
// 啟動runloop方法二
// [[NSRunLoop currentRunLoop] run];
NSLog(@"timer end");
});
}
#pragma mark - 運行循環執行操作方法
- (void)timerAction{
static NSInteger num = 0;
NSLog(@"%ld, %@", (long)num++, [NSThread currentThread]);
// 滿足條件后, 停止當前的運行循環
if (num == 3) {
// 一旦停止了運行循環, 后續代碼才能夠執行,執行完畢后, 線程被自動銷毀
// CFRunLoopStop(self.runloop);
CFRunLoopStop(CFRunLoopGetCurrent());
}
}
運行打印結果如下:
2020-07-27 15:43:13.747811+0800 04-runloop[2686:176056] timer start
2020-07-27 15:43:14.750064+0800 04-runloop[2686:176056] 0, <NSThread: 0x60000183cac0>{number = 7, name = (null)}
2020-07-27 15:43:15.753149+0800 04-runloop[2686:176056] 1, <NSThread: 0x60000183cac0>{number = 7, name = (null)}
2020-07-27 15:43:16.749645+0800 04-runloop[2686:176056] 2, <NSThread: 0x60000183cac0>{number = 7, name = (null)}
2020-07-27 15:43:16.749840+0800 04-runloop[2686:176056] timer end
這里需要說明的是, 如果子線程啟動了runloop,就會執行死循環, 后面的代碼是要等runloop關閉以后才會執行的.
主線程默認是開啟runloop的, 主線程添加定時器是無需手動調用[[NSRunLoop currentRunLoop] run]的.
執行效果如下:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"timer start");
// 在子線程開啟timer,由于子線程的運行循環沒有啟動,所以沒法監聽timer事件
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
// 將timer手動添加到runloop
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
NSLog(@"timer end");
}
// timer start 和 timer end 都會被打印出來
2020-07-27 15:50:43.411545+0800 04-runloop[2798:182053] timer start
2020-07-27 15:50:43.411660+0800 04-runloop[2798:182053] timer end
2020-07-27 15:50:44.412786+0800 04-runloop[2798:182053] 0, <NSThread: 0x600003984d80>{number = 1, name = main}
2020-07-27 15:50:45.412850+0800 04-runloop[2798:182053] 1, <NSThread: 0x600003984d80>{number = 1, name = main}
2020-07-27 15:50:46.411865+0800 04-runloop[2798:182053] 2, <NSThread: 0x600003984d80>{number = 1, name = main}
2020-07-27 15:50:47.412662+0800 04-runloop[2798:182053] 3, <NSThread: 0x600003984d80>{number = 1, name = main}
.....
如果在主線程顯示調用[[NSRunLoop currentRunLoop] run]或者CFRunLoopRun(), 效果和子線程一樣, 都會執行一個死循環, 后面的代碼要等runloop關閉后才能執行
執行效果如下:
NSLog(@"timer start");
// 在子線程開啟timer,由于子線程的運行循環沒有啟動,所以沒法監聽timer事件
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
// 將timer手動添加到runloop
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
// 顯式開啟runloop
[[NSRunLoop currentRunLoop] run];
NSLog(@"timer end");
// timer end 不會被打印出來
2020-07-27 15:53:38.467533+0800 04-runloop[2819:184098] timer start
2020-07-27 15:53:39.468922+0800 04-runloop[2819:184098] 0, <NSThread: 0x60000353cd40>{number = 1, name = main}
2020-07-27 15:53:40.468901+0800 04-runloop[2819:184098] 1, <NSThread: 0x60000353cd40>{number = 1, name = main}
2020-07-27 15:53:41.468883+0800 04-runloop[2819:184098] 2, <NSThread: 0x60000353cd40>{number = 1, name = main}
2020-07-27 15:53:42.468934+0800 04-runloop[2819:184098] 3, <NSThread: 0x60000353cd40>{number = 1, name = main}
2020-07-27 15:53:43.468929+0800 04-runloop[2819:184098] 4, <NSThread: 0x60000353cd40>{number = 1, name = main}
2020-07-27 15:53:44.467979+0800 04-runloop[2819:184098] 5, <NSThread: 0x60000353cd40>{number = 1, name = main}
.....